Redis Functions vs. klassisches Lua-Scripting: Migration in der Praxis
AI generated
SET
TTL
Redis · Scripting · Functions
Redis Functions vs. klassisches Lua-Scripting
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.

12 Min. Lesezeit FUNCTION LOAD · EVAL Redis 7 · Redis 8 · Lua

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.

11. FAQ: Redis Functions vs. Lua-Scripting: Das Wichtigste auf einen Blick

1Was ist der grundlegende Unterschied zwischen EVAL und Redis Functions?
EVAL referenziert anonyme Lua-Skripte über einen aus dem Inhalt berechneten SHA1-Hash, während Functions benannte Bibliotheken mit stabilen Funktionsnamen über FUNCTION LOAD registrieren.
2Wie ruft man eine registrierte Function auf?
Über FCALL mit Funktionsname, Anzahl der Schlüssel, den Schlüsseln und weiteren Argumenten, oder über FCALL_RO für reine Lesefunktionen ohne Schreibzugriff.
3Warum ist die Update-Verwaltung bei Functions einfacher als bei EVAL?
Weil FUNCTION LOAD REPLACE eine Bibliothek vollständig ersetzt, während Funktionsnamen stabil bleiben. Bei EVAL ändert sich der Hash bei jeder Skriptänderung, was Aufrufcode anpassungsbedürftig macht.
4Wie unterscheidet sich das Replikationsverhalten zwischen EVAL und Functions?
Functions erzwingen effektive Replikation standardmäßig, bei EVAL muss ein Skriptautor bei nicht-deterministischen Skripten explizit redis.replicate_commands() aufrufen, was in älteren Skripten oft vergessen wird.
5Welche Flags lassen sich bei einer Function-Registrierung setzen?
Unter anderem no-writes für garantiert lesende Funktionen und allow-stale für Funktionen, die auch mit veralteten Replikat-Daten sinnvoll laufen. Redis erzwingt diese Flags technisch.
6Bleiben geladene Function-Bibliotheken nach einem Neustart erhalten?
Ja, sofern RDB- oder AOF-Persistenz aktiv ist. Bibliotheken werden zudem automatisch an Replikate übertragen, ohne dass sie dort erneut geladen werden müssten.
7Wann bleibt klassisches EVAL trotzdem die bessere Wahl?
Bei einmaligen, ad-hoc ausgeführten Skripten wie manuellen Migrationen oder temporären Debugging-Skripten, bei denen der Aufwand einer dauerhaften Bibliotheksregistrierung unverhältnismäßig wäre.
8Wie läuft eine praktische Migration eines bestehenden EVAL-Skripts ab?
Der Lua-Code wird in eine Funktionsdefinition mit Shebang-Zeile und redis.register_function() verpackt, parallel zu EVALSHA getestet und erst nach Verifikation vollständig auf FCALL umgeschaltet.
9Wie entfernt man eine nicht mehr benötigte Bibliothek?
Mit FUNCTION DELETE bibliotheksname lässt sich eine einzelne Bibliothek gezielt entfernen, ohne andere Bibliotheken zu beeinträchtigen. FUNCTION FLUSH löscht dagegen alle Bibliotheken auf einmal.
10Ist Redis Functions in jeder Redis-Version verfügbar?
Nein, FUNCTION LOAD steht erst ab Redis 7.0 zur Verfügung. In älteren Versionen bleibt EVAL mangels Alternative die einzige Option für Serverlogik.