große Keys löschen, ohne den Server zu blockieren
Ein einzelner DEL-Befehl wirkt harmlos, kann bei einer Collection mit Millionen Elementen aber den gesamten Redis-Server für mehrere hundert Millisekunden einfrieren. Lazy Freeing verlagert die eigentliche Speicherfreigabe in einen Hintergrund-Thread und macht das Löschen großer Keys unabhängig von ihrer internen Größe spürbar schnell.
Inhaltsverzeichnis
- 1. Warum DEL bei großen Keys den Server blockiert
- 2. Wie UNLINK das Problem löst
- 3. Die lazyfree-Konfigurationsoptionen im Überblick
- 4. lazyfree-lazy-user-del: DEL automatisch wie UNLINK behandeln
- 5. Wann sich Lazy Freeing wirklich lohnt
- 6. FLUSHALL und FLUSHDB mit ASYNC-Option
- 7. Zusammenspiel mit Replikation und AOF-Persistenz
- 8. Den Lazy-Free-Hintergrund-Thread beobachten
- 9. Praxis-Fazit: UNLINK als Standard für große Keys etablieren
- 10. Zusammenfassung
- 11. FAQ
1. Warum DEL bei großen Keys den Server blockiert
Redis verarbeitet Commands standardmäßig in einem einzigen Haupt-Thread, der für maximale Vorhersagbarkeit sorgt, weil jeder Befehl vollständig abgeschlossen wird, bevor der nächste beginnt. Bei einfachen Operationen wie GET oder SET auf kleinen Werten dauert das nur Mikrosekunden. Bei DEL auf einer sehr großen Datenstruktur, etwa einer Hash mit zehn Millionen Feldern oder einer Liste mit ebenso vielen Elementen, muss der Haupt-Thread aber jedes einzelne interne Objekt durchlaufen und dessen Speicher freigeben, bevor der Befehl als abgeschlossen gilt.
Genau in diesem Moment zeigt sich das Problem des Single-Thread-Modells: Während der Haupt-Thread mit dem Freigeben von Speicher für diesen einen großen Key beschäftigt ist, kann er keinen einzigen anderen Command für irgendeinen anderen Client verarbeiten, selbst wenn dieser nur ein triviales GET auf einen völlig unabhängigen Key ausführen will. Aus Sicht aller anderen Verbindungen wirkt Redis für die Dauer dieser Freigabe wie eingefroren, was in produktiven Umgebungen zu spürbaren Latenzspitzen und im schlimmsten Fall zu Timeout-Fehlern in der aufrufenden Anwendung führt.
2. Wie UNLINK das Problem löst
UNLINK wurde als asynchrone Alternative zu DEL eingeführt und verhält sich aus Anwendersicht identisch, der Key ist nach dem Befehl sofort nicht mehr sichtbar und nicht mehr auffindbar. Der entscheidende Unterschied liegt in der internen Umsetzung: UNLINK entfernt den Key lediglich aus dem Keyspace-Dictionary, was eine schnelle, konstante Operation ist, und übergibt die eigentliche Speicherfreigabe an einen dedizierten Lazy-Free-Hintergrund-Thread. Dieser Thread läuft parallel zum Haupt-Thread und gibt den Speicher schrittweise frei, ohne die Verarbeitung neuer Commands zu blockieren.
Wichtig ist dabei die Unterscheidung zwischen sichtbarem und physischem Löschen: Sobald UNLINK zurückkehrt, ist der Key aus Sicht aller Clients bereits weg, ein nachfolgendes GET liefert nil, ein EXISTS liefert 0. Der tatsächliche Speicher wird aber erst etwas später im Hintergrund-Thread freigegeben. Für die Anwendungslogik ist dieser Unterschied vollkommen unsichtbar, er wirkt sich nur auf die Geschwindigkeit aus, mit der der Server auf den Löschbefehl reagiert.
# Blockierendes Löschen einer sehr großen Hash
redis-cli DEL riesiger-produktkatalog-cache
# Kann bei zehn Millionen Feldern mehrere hundert Millisekunden dauern
# und blockiert in dieser Zeit ALLE anderen Clients
# Nicht-blockierendes Löschen derselben Hash
redis-cli UNLINK riesiger-produktkatalog-cache
# Key ist sofort verschwunden, Speicherfreigabe läuft im Hintergrund-Thread
3. Die lazyfree-Konfigurationsoptionen im Überblick
Neben dem expliziten UNLINK-Befehl bietet Redis mehrere Konfigurationsoptionen, die interne Löschvorgänge automatisch in den Lazy-Free-Modus versetzen, ohne dass die Anwendung selbst UNLINK aufrufen muss. lazyfree-lazy-eviction steuert, ob Keys, die wegen maxmemory-Druck automatisch entfernt werden, asynchron freigegeben werden. lazyfree-lazy-expire betrifft Keys, die durch Ablauf ihrer TTL entfernt werden, und lazyfree-lazy-server-del betrifft interne Befehle wie RENAME, die zuerst einen alten Key entfernen müssen, bevor der neue Name gilt.
Eine besonders wichtige Option ist lazyfree-lazy-user-flush, die sich auf FLUSHALL und FLUSHDB auswirkt. Ohne diese Option blockiert ein FLUSHALL auf einer großen Datenbank den Server für die gesamte Dauer der Speicherfreigabe, was gerade in Testumgebungen oder bei geplanten Wartungsfenstern zu unerwartet langen Downtimes führen kann. Mit aktivierter Option kehrt der Befehl sofort zurück, während der eigentliche Speicher im Hintergrund freigegeben wird.
# redis.conf: Lazy Freeing für alle relevanten Fälle aktivieren
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
lazyfree-lazy-user-del yes
lazyfree-lazy-user-flush yes
# Zur Laufzeit prüfen und setzen
redis-cli CONFIG GET lazyfree-lazy-user-flush
redis-cli CONFIG SET lazyfree-lazy-user-flush yes
4. lazyfree-lazy-user-del: DEL automatisch wie UNLINK behandeln
Eine oft übersehene Option ist lazyfree-lazy-user-del. Wird sie aktiviert, verhält sich der klassische DEL-Befehl intern genauso wie UNLINK, ohne dass Client-Code oder bestehende Skripte geändert werden müssen. Das ist besonders praktisch für bestehende Anwendungen, in denen DEL an vielen Stellen fest verdrahtet ist und ein flächendeckendes Umschreiben auf UNLINK aufwendig oder riskant wäre.
Der Kompromiss dabei: Diese Einstellung wirkt global für den gesamten Server und lässt sich nicht pro Key oder pro Command-Aufruf granular steuern. Wer einzelne, bewusst synchrone Löschvorgänge benötigt, etwa weil eine nachfolgende Operation sich auf die sofortige, vollständige Speicherfreigabe verlassen muss, sollte diese Option mit Bedacht einsetzen und im Zweifel weiterhin explizit zwischen DEL und UNLINK unterscheiden, statt sich auf globales automatisches Verhalten zu verlassen.
5. Wann sich Lazy Freeing wirklich lohnt
Lazy Freeing entfaltet seinen Nutzen vor allem bei Datenstrukturen, die viele interne Elemente enthalten: große Hashes mit Millionen Feldern, lange Listen, umfangreiche Sorted Sets oder Sets mit sehr vielen Mitgliedern. Bei kleinen Keys, etwa einem einfachen String mit wenigen Bytes, ist der Unterschied zwischen DEL und UNLINK praktisch nicht messbar, weil die Speicherfreigabe ohnehin nur wenige Nanosekunden dauert und selbst synchron keine spürbare Blockade verursacht.
In einem Magento-Kontext betrifft das typischerweise große Full-Page-Cache-Einträge mit umfangreichen HTML-Fragmenten, aggregierte Produktkatalog-Caches oder Session-Container, die über lange Zeit angesammelte Daten enthalten. Wer regelmäßig große Cache-Bereiche invalidiert, etwa nach einem Preisimport oder einer Kategorie-Umstrukturierung, sollte konsequent UNLINK statt DEL verwenden, um Latenzspitzen für alle anderen gleichzeitig aktiven Shop-Besucher zu vermeiden.
6. FLUSHALL und FLUSHDB mit ASYNC-Option
Zusätzlich zur globalen lazyfree-lazy-user-flush-Konfiguration bieten FLUSHALL und FLUSHDB seit Redis 4 ein explizites ASYNC-Argument, das unabhängig von der globalen Konfiguration pro Aufruf gesetzt werden kann. Das ist besonders wertvoll für administrative Skripte oder Wartungswerkzeuge, die nicht davon ausgehen wollen, dass der Server-Betreiber die passende lazyfree-Option bereits global aktiviert hat.
Das Gegenstück dazu ist das explizite SYNC-Argument, das selbst bei aktivierter globaler Lazy-Free-Konfiguration erzwingt, dass der Befehl synchron und blockierend ausgeführt wird. Das ist sinnvoll für Testszenarien oder automatisierte Deployment-Skripte, bei denen garantiert sein muss, dass eine Datenbank vollständig und nachweisbar leer ist, bevor der nächste Schritt beginnt, etwa vor dem Import eines frischen Datensatzes in einer CI-Pipeline.
# Nicht-blockierendes Leeren, unabhängig von globaler Konfiguration
redis-cli FLUSHALL ASYNC
# Erzwungen blockierendes Leeren (z.B. für deterministische CI-Skripte)
redis-cli FLUSHDB SYNC
7. Zusammenspiel mit Replikation und AOF-Persistenz
Ein wichtiger Aspekt, der beim Umstieg auf UNLINK oft übersehen wird: Der Lazy-Free-Mechanismus ist ein rein lokales Implementierungsdetail des jeweiligen Redis-Prozesses und ändert nichts am nach außen sichtbaren Replikationsverhalten. Wird ein UNLINK auf einem Primary-Server ausgeführt, propagiert Redis den Befehl unverändert als UNLINK an alle angebundenen Replicas weiter, sodass jede Replica ihrerseits ebenfalls von der asynchronen Speicherfreigabe profitiert, statt den Befehl in ein blockierendes DEL umzuwandeln.
Auch für die AOF-Persistenz gilt ähnliches: Der UNLINK-Befehl wird als solcher in die AOF-Datei geschrieben, sodass ein späteres Nachspielen des AOF-Logs, etwa nach einem Neustart, ebenfalls die nicht-blockierende Variante verwendet. Wichtig zu wissen ist dabei, dass die eigentliche Hintergrund-Freigabe des Speichers, im Gegensatz zur Befehlspropagierung, ausschließlich lokal auf dem jeweiligen Server stattfindet und nicht zwischen Primary und Replicas synchronisiert werden muss, da jede Instanz ihre eigene, unabhängige Speicherverwaltung besitzt.
8. Den Lazy-Free-Hintergrund-Thread beobachten
Redis stellt über INFO Memory einen Zähler namens lazyfree_pending_objects bereit, der angibt, wie viele Objekte aktuell auf ihre Freigabe durch den Hintergrund-Thread warten. Ein dauerhaft hoher oder stetig wachsender Wert deutet darauf hin, dass mehr Keys gelöscht werden, als der Hintergrund-Thread verarbeiten kann, was auf eine ungewöhnlich hohe Löschrate großer Objekte oder eine unterdimensionierte Server-Konfiguration hindeutet.
Ergänzend zeigt INFO Memory auch den aktuellen used_memory-Wert, der bei einer Verzögerung im Lazy-Free-Thread kurzzeitig höher bleibt, als man nach dem Löschen eines großen Keys erwarten würde, weil der Speicher eben noch nicht vollständig freigegeben wurde. Beim Monitoring von Redis-Instanzen lohnt es sich, diesen Zähler in das reguläre Metriken-Dashboard aufzunehmen, gerade in Umgebungen mit häufigen Löschvorgängen großer Datenstrukturen.
9. Praxis-Fazit: UNLINK als Standard für große Keys etablieren
Für neue Anwendungen und Skripte lohnt es sich, UNLINK direkt als Standard-Befehl für das Löschen von Keys zu etablieren, statt sich auf globale lazyfree-Konfiguration zu verlassen, die je nach Deployment-Umgebung unterschiedlich gesetzt sein kann. Für bestehenden Code, in dem DEL breit verwendet wird, ist lazyfree-lazy-user-del ein pragmatischer Weg, das blockierende Verhalten global abzuschalten, ohne jede einzelne Codestelle anfassen zu müssen.
Wichtig bleibt, dass Lazy Freeing kein Allheilmittel ist: Es verlagert die Kosten der Speicherfreigabe lediglich in einen Hintergrund-Thread, es eliminiert sie nicht. Bei extrem hoher Löschrate großer Objekte kann auch der Hintergrund-Thread ins Hintertreffen geraten, was sich im ansteigenden lazyfree_pending_objects-Zähler zeigt und ein Signal für eine grundsätzliche Überprüfung der Datenmodellierung sein sollte, etwa ob wirklich derart große einzelne Keys nötig sind.
| Konfigurationsoption | Betrifft | Effekt bei yes | Empfehlung |
|---|---|---|---|
| lazyfree-lazy-eviction | Automatische Eviction bei maxmemory | Asynchrone Freigabe statt Blockade | Aktivieren bei großen Keys |
| lazyfree-lazy-expire | Ablauf per TTL | Asynchrone Freigabe abgelaufener Keys | Aktivieren bei großen Keys |
| lazyfree-lazy-user-del | Klassisches DEL | DEL verhält sich wie UNLINK | Sinnvoll bei bestehendem Code |
| lazyfree-lazy-user-flush | FLUSHALL / FLUSHDB | Nicht-blockierendes Leeren | Aktivieren in produktiven Umgebungen |
| ASYNC-Argument bei FLUSHALL | Einzelaufruf, überschreibt Config | Nicht-blockierend für diesen Aufruf | Für administrative Skripte |
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
Lazy Freeing
DEL blockiert bei großen Keys
Der Single-Thread wartet, bis der gesamte Speicher eines großen Keys freigegeben ist.
UNLINK löst sofort ab
Der Key verschwindet sofort sichtbar, die Speicherfreigabe läuft im Hintergrund-Thread.
lazyfree-Optionen automatisieren
Eviction, Expire, Flush und sogar DEL lassen sich global auf asynchrones Verhalten umstellen.
Monitoring nicht vergessen
lazyfree_pending_objects in INFO Memory zeigt, ob der Hintergrund-Thread mit der Last mithält.