Migration in der Praxis
Klassisches Lua-Scripting mit EVAL und EVALSHA ist seit Jahren der Standardweg für atomare Serverlogik in Redis, bringt aber praktische Probleme bei Wartbarkeit, Versionierung und Replikationsverhalten mit sich. Seit Redis 7 stehen mit Redis Functions eine strukturiertere Alternative über FUNCTION LOAD zur Verfügung, die Skripte als benannte, dauerhaft gespeicherte Bibliotheken behandelt statt als flüchtige, über Hashes referenzierte Einzelskripte. Wie der Umstieg konkret aussieht, welche Vorteile er bringt und wo EVAL trotzdem die bessere Wahl bleibt, zeigt dieser Artikel.
Inhaltsverzeichnis
- 1. Warum klassisches EVAL in der Praxis an Grenzen stößt
- 2. Das Grundprinzip von Redis Functions: benannte Bibliotheken statt anonymer Skripte
- 3. Eine Bibliothek laden und Funktionen aufrufen
- 4. Vorteil Wartbarkeit: benannte Bibliotheken statt Hash-Zoo
- 5. Vorteil Replikation: effektive Replikation statt Skript-Propagierung
- 6. Praktischer Migrationspfad: ein bestehendes EVAL-Skript umbauen
- 7. Wann EVAL trotzdem die bessere Wahl bleibt
- 8. Zusätzliche Kontrolle: Flags für Lesezugriff und Zeitverhalten
- 9. Operative Verwaltung: Übersicht, Löschen und Persistenz von Bibliotheken
- 10. Zusammenfassung
- 11. FAQ
1. Warum klassisches EVAL in der Praxis an Grenzen stößt
Ein Lua-Skript, das per EVAL ausgeführt wird, existiert für Redis zunächst nur als anonymer Textblock, der bei jedem Aufruf mitgeschickt werden müsste, wäre da nicht die serverseitige Skript-Zwischenspeicherung über SCRIPT LOAD und der darauf folgende Aufruf per SHA1-Hash mit EVALSHA. Dieses Modell funktioniert, bringt aber ein strukturelles Problem mit: Der Hash ist eine reine Kennung ohne semantische Bedeutung, sodass Anwendungscode üblicherweise selbst Buch darüber führen muss, welcher Hash zu welchem Skript gehört, und bei einem Server-Neustart oder SCRIPT FLUSH müssen alle Skripte erneut geladen werden, bevor EVALSHA wieder funktioniert.
Zusätzlich gibt es keine eingebaute Versionierung: Ändert sich ein Skript, ändert sich auch sein Hash, und alte, noch im Umlauf befindliche Hashes werden ungültig, ohne dass Redis selbst irgendeine Übersicht führt, welche Skriptversionen aktuell tatsächlich im Einsatz sind. Für einzelne, kleine, selten geänderte Skripte ist das kein großes Problem, für eine wachsende Sammlung an dauerhaft genutzter Serverlogik wird es schnell unübersichtlich.
2. Das Grundprinzip von Redis Functions: benannte Bibliotheken statt anonymer Skripte
Redis Functions kehren dieses Modell um: Statt einzelner, über Hashes referenzierter Skripte lädt man mit FUNCTION LOAD eine vollständige, namentlich identifizierte Bibliothek, die eine oder mehrere benannte Funktionen enthält. Jede Funktion wird über ihren Namen aufgerufen, etwa mit FCALL myfunction 1 mykey, nicht über einen aus dem Skriptinhalt berechneten Hash. Der Bibliotheksname selbst ist Teil der persistenten Redis-Konfiguration und übersteht damit Neustarts, ohne dass die Anwendung selbst Buch über Hashes führen müsste.
Technisch bleibt Lua weiterhin die Skriptsprache, Functions sind also keine neue Programmiersprache, sondern eine strukturiertere Hülle um dieselbe Lua-Ausführungsumgebung, ergänzt um ein festes Registrierungsschema über redis.register_function() innerhalb der Bibliotheksdefinition.
#!lua name=cart_library
redis.register_function('cart_add_item', function(keys, args)
local cart_key = keys[1]
local item_id = args[1]
local qty = tonumber(args[2])
redis.call('HINCRBY', cart_key, item_id, qty)
redis.call('EXPIRE', cart_key, 3600)
return redis.call('HGETALL', cart_key)
end)
3. Eine Bibliothek laden und Funktionen aufrufen
Das Laden einer Bibliothek erfolgt mit FUNCTION LOAD, gefolgt vom vollständigen Bibliotheksquelltext, der mit einer Shebang-Zeile #!lua name=bibliotheksname beginnen muss. Diese Namensbindung ist verpflichtend und sorgt dafür, dass Redis Namenskonflikte zwischen unterschiedlichen Bibliotheken sofort erkennt, statt sie erst zur Laufzeit als schwer diagnostizierbaren Fehler auffallen zu lassen.
Der Aufruf einer registrierten Funktion erfolgt über FCALL oder, für reine Lesefunktionen ohne Schreibzugriff, über FCALL_RO, jeweils mit dem Funktionsnamen, der Anzahl der übergebenen Schlüssel und den Schlüsseln sowie zusätzlichen Argumenten. Diese explizite Trennung zwischen Schlüsseln und sonstigen Argumenten entspricht dem bekannten Muster von EVAL, sodass sich bestehende Aufrufkonventionen weitgehend übertragen lassen.
# Bibliothek aus einer Datei laden
redis-cli -x FUNCTION LOAD < cart_library.lua
# Registrierte Funktion aufrufen: 1 Schlüssel, zwei Argumente
FCALL cart_add_item 1 cart:4711 sku-123 2
# Alle geladenen Bibliotheken und ihre Funktionen anzeigen
FUNCTION LIST
4. Vorteil Wartbarkeit: benannte Bibliotheken statt Hash-Zoo
Der zentrale Wartbarkeitsvorteil von Functions liegt darin, dass eine Bibliothek als Ganzes ausgetauscht wird: FUNCTION LOAD REPLACE ersetzt eine bestehende Bibliothek vollständig durch eine neue Version, wobei alle enthaltenen Funktionsnamen stabil bleiben, solange sie in der neuen Version weiterhin definiert sind. Anwendungscode, der Funktionen über ihren Namen aufruft, muss bei einem Update also nicht angepasst werden, im Gegensatz zu EVALSHA-Aufrufen, die nach jeder Skriptänderung einen neuen Hash referenzieren müssen.
Zusätzlich lässt sich der komplette Bibliotheksquelltext jederzeit über FUNCTION DUMP sichern und über FUNCTION RESTORE wiederherstellen, was eine saubere Versionsverwaltung außerhalb von Redis erlaubt, etwa als Teil eines Deployment-Repositories, in dem jede Bibliotheksversion als eigene, nachvollziehbare Datei vorliegt, statt als Sammlung schwer zuordenbarer Hash-Werte.
5. Vorteil Replikation: effektive Replikation statt Skript-Propagierung
Bei klassischem EVAL propagiert Redis standardmäßig die tatsächlich ausgeführten, resultierenden Schreibbefehle an Replikate und ins AOF, nicht den Skriptaufruf selbst, sofern das Skript als deterministisch markiert ist. Bei nicht-deterministischen Skripten, etwa solchen, die auf TIME oder RANDOMKEY zugreifen, muss der Skriptautor explizit auf effektive Replikation über redis.replicate_commands() umschalten, was in älteren Skripten häufig vergessen wird und dann zu Inkonsistenzen zwischen Primär- und Replikat-Instanz führen kann.
Redis Functions erzwingen effektive Replikation von vornherein als Standardverhalten, ohne dass der Funktionsautor sich explizit darum kümmern muss. Jede innerhalb einer Function ausgeführte Schreiboperation wird einzeln und deterministisch an Replikate propagiert, was das Risiko stiller Inkonsistenzen durch vergessene Replikationsanweisungen strukturell beseitigt, statt es der Disziplin des Skriptautors zu überlassen.
6. Praktischer Migrationspfad: ein bestehendes EVAL-Skript umbauen
Der Umstieg eines bestehenden EVAL-Skripts beginnt mit der Identifikation aller produktiv genutzten Skripte, üblicherweise über SCRIPT LIST beziehungsweise über eine Bestandsaufnahme im Anwendungscode, da Redis selbst keine Beschreibung mitführt, wofür ein Skript inhaltlich gedacht ist. Anschließend verpackt man den bestehenden Lua-Code in eine Funktionsdefinition mit Shebang-Zeile und redis.register_function()-Aufruf, wobei die eigentliche Logik meist unverändert übernommen werden kann, da die zugrunde liegende Lua-Ausführungsumgebung dieselbe bleibt.
Die Anwendung wird dann parallel auf beide Aufrufwege vorbereitet, ruft also testweise sowohl EVALSHA als auch FCALL auf, vergleicht die Ergebnisse in einer Staging-Umgebung und schaltet erst nach erfolgreicher Verifikation vollständig auf FCALL um. Alte, nicht mehr benötigte EVAL-basierte Skripte können danach schrittweise aus dem Anwendungscode entfernt werden, ohne dass ein harter Cutover-Zeitpunkt nötig wäre.
-- Vorher: klassisches EVAL-Skript (atomarer Zaehler mit Limit)
-- KEYS[1] = counter key, ARGV[1] = max value
if tonumber(redis.call('GET', KEYS[1]) or '0') >= tonumber(ARGV[1]) then
return 0
end
return redis.call('INCR', KEYS[1])
-- Nachher: dieselbe Logik als registrierte Function
#!lua name=counter_library
redis.register_function('counter_incr_limited', function(keys, args)
local current = tonumber(redis.call('GET', keys[1]) or '0')
if current >= tonumber(args[1]) then
return 0
end
return redis.call('INCR', keys[1])
end)
7. Wann EVAL trotzdem die bessere Wahl bleibt
Für einmalige, ad-hoc ausgeführte Skripte, etwa eine manuelle Datenmigration im Rahmen einer einzelnen Wartungsaktion oder ein Debugging-Skript, das nur während einer Fehleranalyse temporär läuft, ist der zusätzliche Aufwand einer benannten Bibliothek mit Registrierung unverhältnismäßig. Hier bleibt EVAL mit direkt mitgeschicktem Skriptinhalt weiterhin die pragmatischere Wahl, weil keine dauerhafte Registrierung und kein Aufräumen einer Bibliothek nötig ist.
Auch in sehr alten Redis-Versionen vor 7.0, die in manchen produktiven Umgebungen aus Kompatibilitätsgründen noch im Einsatz sind, steht FUNCTION LOAD schlicht nicht zur Verfügung, sodass EVAL dort mangels Alternative die einzige Option bleibt. Bei einem geplanten Upgrade auf Redis 7 oder neuer lohnt sich dann eine bewusste Entscheidung, welche der bestehenden Skripte tatsächlich von einer Migration zu Functions profitieren und welche als einmalige Werkzeuge besser bei EVAL bleiben.
8. Zusätzliche Kontrolle: Flags für Lesezugriff und Zeitverhalten
Bei der Registrierung einer Funktion lassen sich über eine Flags-Tabelle zusätzliche Eigenschaften deklarieren, etwa no-writes für Funktionen, die garantiert keine Schreibbefehle ausführen und deshalb sicher über FCALL_RO auf Replikaten aufgerufen werden dürfen, oder allow-stale für Funktionen, die auch auf einem Replikat mit veralteten Daten sinnvoll ausgeführt werden können. Diese Flags werden von Redis selbst durchgesetzt, ein Funktionsaufruf mit dem no-writes-Flag, der dennoch einen Schreibbefehl enthält, wird mit einem Fehler abgelehnt.
Bei klassischem EVAL existiert keine vergleichbare deklarative Absicherung: Ob ein Skript tatsächlich nur liest, muss der Aufrufer selbst wissen und durch Konvention sicherstellen, ohne dass Redis dies technisch erzwingt. Diese zusätzliche Kontrollebene ist ein weiterer, oft unterschätzter Wartbarkeitsvorteil von Functions gegenüber EVAL, besonders in Teams mit mehreren Entwicklern, die dieselbe Bibliothek pflegen.
#!lua name=readonly_library
redis.register_function{
function_name='cart_get_total',
callback=function(keys, args)
local items = redis.call('HGETALL', keys[1])
local total = 0
for i = 2, #items, 2 do
total = total + tonumber(items[i])
end
return total
end,
flags={'no-writes'}
}
9. Operative Verwaltung: Übersicht, Löschen und Persistenz von Bibliotheken
Geladene Bibliotheken werden standardmäßig persistent behandelt: Sie überstehen einen Neustart, sofern RDB- oder AOF-Persistenz aktiv ist, und werden bei einer Replikation automatisch an Replikate übertragen, ohne dass die Anwendung sie dort erneut laden müsste. Mit FUNCTION LIST WITHCODE lässt sich der vollständige Quelltext aller geladenen Bibliotheken samt Metadaten wie Funktionsnamen und Flags abrufen, was sich gut für automatisierte Konsistenzprüfungen zwischen erwarteter und tatsächlich geladener Version eignet.
Nicht mehr benötigte Bibliotheken lassen sich gezielt mit FUNCTION DELETE bibliotheksname entfernen, ohne andere Bibliotheken zu beeinträchtigen, während FUNCTION FLUSH sämtliche geladenen Bibliotheken auf einmal löscht und deshalb nur bewusst und selten eingesetzt werden sollte, etwa im Rahmen eines kontrollierten Rollbacks.
| Merkmal | Klassisches EVAL/EVALSHA | Redis Functions | Praxisrelevanz |
|---|---|---|---|
| Identifikation | SHA1-Hash des Skriptinhalts | Benannte Bibliothek und Funktion | Functions ohne eigene Hash-Buchführung |
| Update-Verhalten | Neuer Hash bei jeder Änderung | FUNCTION LOAD REPLACE, Name bleibt stabil | Aufrufcode muss bei Functions nicht angepasst werden |
| Replikation | Effektive Replikation optional, muss aktiviert werden | Effektive Replikation standardmäßig aktiv | Functions strukturell konsistenter |
| Deklarative Flags | Nicht vorhanden | no-writes, allow-stale und weitere | Functions erzwingen Absicherung technisch |
| Geeignet für | Einmalige Ad-hoc-Skripte | Dauerhaft genutzte Serverlogik | Wahl je nach Lebensdauer des Skripts treffen |
Mironsoft
Cache-Layer-Setup und Magento-Redis-Integration
Magento-Cache, der nicht richtig greift oder falsch konfiguriert ist?
Wir richten Redis als Cache- und Session-Backend für Magento sauber ein, tunen Speicherverbrauch und Eviction-Strategien und sorgen dafür, dass Full Page Cache und Session-Storage zuverlässig zusammenspielen.
Redis-Setup
Cache-, Session- und FPC-Backend produktionsreif für Magento konfigurieren.
Memory-Tuning
Speicherverbrauch und Eviction-Policies auf die tatsächliche Shop-Last abstimmen.
High-Availability-Setup
Redis Sentinel oder Cluster für ausfallsichere Magento-Umgebungen einrichten.
10. Zusammenfassung
Redis Functions vs. Lua-Scripting: Das Wichtigste auf einen Blick
Grundunterschied
EVAL referenziert anonyme Skripte über Hashes, Functions laden benannte Bibliotheken über FUNCTION LOAD mit stabilen Funktionsnamen.
Wartbarkeitsvorteil
FUNCTION LOAD REPLACE tauscht eine Bibliothek als Ganzes aus, ohne dass Aufrufcode angepasst werden muss.
Replikationsvorteil
Functions erzwingen effektive Replikation standardmäßig, EVAL erfordert dafür explizites redis.replicate_commands().
Migrationsstrategie
Bestehende Skripte schrittweise in registrierte Funktionen umwandeln, parallel testen und erst nach Verifikation vollständig umschalten.