Lazy Freeing in Redis: große Keys löschen, ohne den Server zu blockieren
AI generated
SET
TTL
Redis / Performance-Tuning
Lazy Freeing
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.

9 Min. Lesezeit Lazy Freeing UNLINK DEL Single-Thread Speicherfreigabe

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.

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.

11. FAQ: Lazy Freeing

1Was ist der genaue Unterschied zwischen DEL und UNLINK?
Beide entfernen einen Key sofort sichtbar aus dem Keyspace. DEL gibt den zugehörigen Speicher synchron im Haupt-Thread frei, was bei großen Datenstrukturen den Server blockiert. UNLINK entfernt den Key ebenfalls sofort aus dem Keyspace, übergibt die eigentliche Speicherfreigabe aber an einen Hintergrund-Thread, sodass der Haupt-Thread weiterhin andere Commands verarbeiten kann.
2Ist UNLINK grundsätzlich schneller als DEL?
Aus Sicht des aufrufenden Clients kehrt UNLINK bei großen Keys deutlich schneller zurück, weil nur der Keyspace-Eintrag entfernt werden muss. Bei kleinen Keys ist der Unterschied kaum messbar, da die Speicherfreigabe dort ohnehin nur Nanosekunden dauert.
3Kann ich DEL einfach überall durch UNLINK ersetzen?
In den allermeisten Fällen ja, da sich beide Befehle aus Anwendersicht identisch verhalten. Eine Ausnahme sind Fälle, in denen eine Anwendung sich explizit auf eine vollständige, sofortige Speicherfreigabe verlassen muss, etwa vor einer harten Speicherlimit-Prüfung direkt im Anschluss.
4Was macht lazyfree-lazy-user-del genau?
Diese Option sorgt dafür, dass der klassische DEL-Befehl serverintern automatisch wie UNLINK behandelt wird. So profitiert bestehender Code, der DEL verwendet, vom nicht-blockierenden Verhalten, ohne dass der Code selbst geändert werden muss.
5Wie erkenne ich, ob der Lazy-Free-Hintergrund-Thread überlastet ist?
Über INFO Memory lässt sich der Zähler lazyfree_pending_objects auslesen. Ein dauerhaft hoher oder stetig wachsender Wert zeigt, dass mehr Objekte zur Freigabe anstehen, als der Hintergrund-Thread verarbeiten kann.
6Blockiert FLUSHALL den Server auch ohne besonders große einzelne Keys?
Ja, wenn die Datenbank insgesamt sehr viele Keys enthält, summiert sich die Speicherfreigabe über alle Keys hinweg zu einer spürbaren Blockade, selbst wenn kein einzelner Key für sich genommen riesig ist. Das ASYNC-Argument oder die lazyfree-lazy-user-flush-Option lösen dieses Problem.
7Gibt es einen Nachteil bei der dauerhaften Aktivierung aller lazyfree-Optionen?
Der Hauptnachteil ist ein zusätzlicher, wenn auch meist geringer, CPU- und Speicher-Overhead durch den zusätzlichen Hintergrund-Thread sowie eine kurze Verzögerung zwischen sichtbarem Löschen und tatsächlicher Speicherfreigabe. Für die allermeisten produktiven Setups überwiegt der Vorteil der Nicht-Blockade deutlich.
8Funktioniert Lazy Freeing auch bei Redis Cluster und Replikation?
Ja, Lazy Freeing ist ein lokaler Mechanismus des jeweiligen Redis-Prozesses und funktioniert unabhängig davon, ob eine Instanz Teil eines Clusters oder einer Replikationstopologie ist. Bei Replikation wird der Löschbefehl an Replikas weitergereicht und dort ebenfalls gemäß deren eigener lazyfree-Konfiguration behandelt.
9Was passiert, wenn der Redis-Prozess abstürzt, während der Hintergrund-Thread noch Speicher freigibt?
Da es sich um reine In-Memory-Speicherverwaltung innerhalb desselben Prozesses handelt, ist das kein Problem im Sinne von Datenverlust: Der Key war bereits gelöscht und aus dem Keyspace entfernt, bevor der Hintergrund-Thread aktiv wurde. Ein Absturz beendet lediglich den gesamten Prozess inklusive seines Speichers, unabhängig vom Freigabestatus.
10Ab welcher Redis-Version stehen UNLINK und die lazyfree-Optionen zur Verfügung?
UNLINK und die grundlegenden lazyfree-Konfigurationsoptionen wurden mit Redis 4.0 eingeführt. Das ASYNC-Argument für FLUSHALL und FLUSHDB ist ebenfalls seit Redis 4.0 verfügbar, in aktuellen Redis-8-Installationen sind alle diese Mechanismen selbstverständlich vorhanden.