Redis Expire und TTL-Strategien richtig einsetzen
AI generated
SET
TTL
Redis · TTL · EXPIRE · Cache-Strategien
Redis Expire und TTL-Strategien richtig einsetzen
von EXPIRE bis zum TTL-Jitter gegen Thundering Herd

Redis TTL-Werte bestimmen nicht nur, wann ein Key verschwindet, sondern beeinflussen direkt die Systemlast beim Ablauf. Wer Tausende Keys mit identischer TTL setzt, riskiert einen Thundering-Herd-Effekt, bei dem alle Caches gleichzeitig leerlaufen und das Backend mit Anfragen ueberfluten. TTL-Jitter loest genau dieses Problem.

13 Min. Lesezeit EXPIRE · TTL · PERSIST · TTL-Jitter Redis 6.x · 7.x · redis-cli

1. Was Redis TTL-Strategien wirklich bewirken

Eine Redis TTL-Strategie ist weit mehr als das simple Setzen einer Ablaufzeit fuer einen Key. TTL, kurz fuer Time To Live, definiert die Lebensdauer eines Keys in Sekunden oder Millisekunden, nach deren Ablauf Redis den Key automatisch entfernt. Auf den ersten Blick wirkt das trivial, doch die konkrete Wahl der TTL-Werte und die Verteilung dieser Werte ueber viele Keys hinweg hat direkten Einfluss auf die Systemstabilitaet, besonders in Cache-Architekturen mit hoher Last.

In der Praxis zeigt sich der Unterschied zwischen naiver und durchdachter TTL-Strategie meist erst unter Last: Ein naiver Ansatz setzt fuer alle Cache-Eintraege eines Typs dieselbe feste TTL, etwa eine Stunde. Das funktioniert lange unauffaellig, bis der Moment kommt, an dem viele Keys gleichzeitig ablaufen und alle betroffenen Anfragen gleichzeitig am Cache vorbei direkt auf das Backend treffen. Eine durchdachte Redis TTL-Strategie verteilt Ablaufzeiten bewusst, um genau diesen Effekt zu vermeiden.

Die folgenden Abschnitte behandeln sowohl die Grundlagen von EXPIRE, TTL und PERSIST als auch die internen Mechanismen aktiver und passiver Expiration, bevor konkrete TTL-Strategien wie Jitter, Refresh-Ahead und Soft-TTL vorgestellt werden, die in produktiven Redis-Umgebungen fuer stabile Lastprofile sorgen.

2. EXPIRE, TTL und PERSIST im Ueberblick

EXPIRE key seconds setzt eine relative Ablaufzeit in Sekunden auf einen bestehenden Key, PEXPIRE arbeitet analog mit Millisekunden fuer feinere Praezision. Seit Redis 7.0 unterstuetzen beide Befehle zusaetzliche Optionen wie NX, XX, GT und LT, mit denen sich bedingte TTL-Aenderungen ausdruecken lassen, etwa GT, um eine TTL nur zu setzen, wenn sie laenger ist als die aktuelle. Das ist besonders nuetzlich, wenn mehrere Prozesse denselben Key mit unterschiedlichen TTL-Anforderungen aktualisieren koennten.

TTL key liefert die verbleibende Lebensdauer in Sekunden zurueck, minus 1 signalisiert, dass der Key existiert, aber keine TTL gesetzt hat, minus 2 bedeutet, dass der Key gar nicht existiert. PERSIST key entfernt eine gesetzte TTL vollstaendig und macht den Key wieder dauerhaft, ohne den Wert selbst zu veraendern. Dieser Befehl ist wichtig fuer Faelle, in denen ein temporaerer Cache-Eintrag nachtraeglich zu einem permanenten Datensatz befoerdert werden soll, etwa wenn ein Warenkorb zu einer Bestellung wird.


# Set a relative TTL of 3600 seconds on an existing key
redis-cli> SET session:abc123 "user-data" EX 3600
OK

redis-cli> TTL session:abc123
(integer) 3600

# Conditional TTL update since Redis 7.0: only extend, never shorten
redis-cli> EXPIRE session:abc123 7200 GT
(integer) 1

redis-cli> TTL session:abc123
(integer) 7200

# Attempting a shorter TTL with GT is rejected
redis-cli> EXPIRE session:abc123 1800 GT
(integer) 0

# PERSIST removes the TTL entirely, the key becomes permanent
redis-cli> PERSIST session:abc123
(integer) 1

redis-cli> TTL session:abc123
(integer) -1

3. Passive Expiration: Ablauf bei Zugriff

Redis implementiert zwei komplementaere Mechanismen, um abgelaufene Keys tatsaechlich zu entfernen, und passive Expiration ist der einfachere der beiden. Immer wenn ein Client mit GET, EXISTS oder einem anderen lesenden Befehl auf einen Key zugreift, prueft Redis vor der eigentlichen Verarbeitung, ob die gesetzte TTL bereits abgelaufen ist. Ist das der Fall, wird der Key sofort geloescht und der Client erhaelt eine Antwort, als haette der Key nie existiert, etwa nil bei GET.

Der entscheidende Punkt bei passiver Expiration: Ohne einen tatsaechlichen Zugriff wuerde ein abgelaufener Key theoretisch unbegrenzt im Speicher verbleiben, weil Redis ihn nie aktiv anfasst. Fuer selten gelesene Keys mit sehr kurzer TTL wuerde das bedeuten, dass abgelaufene, aber ungenutzte Daten den Arbeitsspeicher unnoetig belegen. Genau diese Luecke schliesst der zweite Mechanismus, die aktive Expiration.

4. Aktive Expiration: der Hintergrund-Zyklus

Aktive Expiration laeuft als periodischer Hintergrundprozess, der standardmaessig zehnmal pro Sekunde ausgefuehrt wird, konfigurierbar ueber den Parameter hz in der Redis-Konfiguration. Bei jedem Durchlauf waehlt Redis eine zufaellige Stichprobe von Keys mit gesetzter TTL aus der internen Expire-Tabelle, prueft, wie viele davon bereits abgelaufen sind, und loescht die abgelaufenen Keys sofort. Liegt der Anteil abgelaufener Keys in der Stichprobe ueber 25 Prozent, wiederholt Redis den Durchlauf sofort erneut, statt bis zum naechsten Zyklus zu warten.

Dieser adaptive Mechanismus sorgt dafuer, dass Redis bei vielen gleichzeitig ablaufenden Keys die Aufraeumarbeit beschleunigt, ohne dabei den Hauptprozess durch eine vollstaendige Iteration ueber alle Keys zu blockieren. Die Stichprobengroesse ist bewusst klein gehalten, standardmaessig 20 Keys pro Datenbank und Durchlauf, um die Latenz des Single-Threaded-Event-Loops nicht spuerbar zu beeintraechtigen. Das ist ein klassischer Trade-off zwischen sofortiger Speicherfreigabe und Antwortzeit-Stabilitaet.


# Inspect the active expiration cycle frequency (default 10 Hz)
redis-cli> CONFIG GET hz
1) "hz"
2) "10"

# Set many keys with a short TTL to observe active expiration at work
redis-cli> MSET temp:1 a temp:2 b temp:3 c
OK
redis-cli> EXPIRE temp:1 1
(integer) 1
redis-cli> EXPIRE temp:2 1
(integer) 1
redis-cli> EXPIRE temp:3 1
(integer) 1

# After ~1 second, the background cycle removes them without any GET
redis-cli> DBSIZE
(integer) 3
# ... wait 1-2 seconds ...
redis-cli> DBSIZE
(integer) 0

5. Wie beide Mechanismen zusammenspielen

Passive und aktive Expiration ergaenzen sich, statt sich gegenseitig zu ersetzen. Passive Expiration garantiert, dass ein Client niemals einen logisch abgelaufenen Wert zurueckbekommt, selbst wenn der Hintergrund-Zyklus den betreffenden Key noch nicht erreicht hat. Aktive Expiration wiederum stellt sicher, dass Speicher auch bei ungenutzten, abgelaufenen Keys zeitnah freigegeben wird, ohne auf einen zufaelligen Lesezugriff warten zu muessen.

Fuer Replikations-Setups gilt eine wichtige Sonderregel: Nur der Master fuehrt aktive und passive Expiration tatsaechlich durch und generiert bei jedem Loeschvorgang einen expliziten DEL-Befehl, der an alle Replicas propagiert wird. Ein Replica loescht abgelaufene Keys niemals selbststaendig, sondern wartet auf diesen DEL-Befehl vom Master. Das verhindert Inkonsistenzen zwischen Master und Replicas, die entstehen wuerden, wenn beide Seiten unabhaengig voneinander ueber den Ablaufzeitpunkt entscheiden wuerden.

6. Das Thundering-Herd-Problem bei synchronen TTLs

Das Thundering-Herd-Problem entsteht, wenn eine grosse Anzahl von Cache-Eintraegen mit identischer oder sehr aehnlicher TTL gleichzeitig ablaeuft. Ein typisches Szenario: Ein Nightly-Batch-Job befuellt zehntausende Produktdaten-Keys im Cache, alle mit einer festen TTL von genau einer Stunde. Nach genau einer Stunde laufen alle diese Keys nahezu gleichzeitig ab, und die naechste Welle eingehender Anfragen trifft nicht mehr auf den Cache, sondern direkt auf die Datenbank oder das Backend-System.

Die Folge ist ein ploetzlicher, massiver Lastanstieg auf dem Backend, der im schlimmsten Fall zu Timeouts, Verbindungspool-Erschoepfung oder einem kompletten Ausfall fuehrt, waehrend gleichzeitig der Cache versucht, alle betroffenen Werte parallel neu zu laden. Dieser selbstverstaerkende Effekt ist besonders tueckisch, weil er im Normalbetrieb unsichtbar bleibt und erst bei ausreichend hoher Last oder ausreichend grosser Anzahl gleichzeitig gesetzter Keys sichtbar wird, oft erst Wochen nach dem initialen Deployment.

7. TTL-Jitter in der Praxis implementieren

TTL-Jitter loest das Thundering-Herd-Problem, indem zur Basis-TTL ein kleiner, zufaelliger Offset addiert wird, sodass Keys, die eigentlich gleichzeitig ablaufen wuerden, stattdessen ueber ein Zeitfenster verteilt ablaufen. Statt einer festen TTL von 3600 Sekunden fuer alle Keys setzt man beispielsweise eine TTL zwischen 3600 und 3900 Sekunden, zufaellig pro Key gewaehlt. Bei zehntausend Keys verteilt sich der Ablauf dann ueber ein fuenfminuetiges Fenster statt auf einen einzigen Moment, was die Backend-Last auf ein handhabbares Niveau senkt.

Die Groesse des Jitter-Fensters haengt vom konkreten Anwendungsfall ab: Ein Jitter von 5 bis 10 Prozent der Basis-TTL ist ein gaengiger Ausgangspunkt, der bei den meisten Cache-Workloads einen guten Kompromiss zwischen Datenaktualitaet und Lastverteilung darstellt. Wichtig ist, den Jitter beim Schreiben des Keys zu berechnen, nicht global fuer alle Keys eines Batches gleich zu setzen, sonst entsteht erneut eine synchrone Gruppe, nur mit verschobener, aber weiterhin gemeinsamer TTL.


# Without jitter: all keys expire at exactly the same moment
redis-cli> SET product:1001 "..." EX 3600
redis-cli> SET product:1002 "..." EX 3600
redis-cli> SET product:1003 "..." EX 3600

# With jitter: base TTL plus a random offset per key (application side)
# base_ttl = 3600, jitter = random(0, 300) computed per key before SET
redis-cli> SET product:1001 "..." EX 3742
redis-cli> SET product:1002 "..." EX 3618
redis-cli> SET product:1003 "..." EX 3891

-- Server-side jitter inside a Lua script keeps jitter logic close to the write path
-- redis-cli EVAL "$(cat set_with_jitter.lua)" 1 product:1001 "..." 3600 300
local base_ttl = tonumber(ARGV[2])
local jitter_max = tonumber(ARGV[3])
local jitter = math.random(0, jitter_max)
redis.call("SET", KEYS[1], ARGV[1], "EX", base_ttl + jitter)
return base_ttl + jitter

8. Weitere TTL-Strategien: Refresh-Ahead und Soft-TTL

Neben TTL-Jitter existieren weitere Strategien, die auf verwandte Probleme zielen. Refresh-Ahead erneuert einen Cache-Eintrag proaktiv, bevor er tatsaechlich ablaeuft, typischerweise wenn die verbleibende TTL unter einen Schwellenwert wie 20 Prozent der urspruenglichen TTL faellt. Dazu wird bei jedem Lesezugriff die verbleibende TTL mit TTL key geprueft, und falls der Schwellenwert unterschritten ist, wird der Wert asynchron neu berechnet und mit frischer TTL zurueckgeschrieben, waehrend der aktuelle Client weiterhin den noch gueltigen alten Wert erhaelt.

Soft-TTL trennt die technische Redis-TTL von einer im Wert selbst gespeicherten logischen Ablaufzeit. Der Cache-Eintrag traegt zusaetzlich einen Zeitstempel, ab dem er als veraltet gilt, waehrend die technische Redis-TTL deutlich laenger gesetzt ist. Bei einem Zugriff auf einen logisch veralteten, aber technisch noch vorhandenen Wert kann die Anwendung entscheiden, den alten Wert trotzdem kurzfristig auszuliefern, waehrend im Hintergrund eine Aktualisierung angestossen wird, ein Muster bekannt als Stale-While-Revalidate. Das verhindert, dass ein Cache-Miss unter Last jemals eine direkte, synchrone Backend-Anfrage erzwingt.

9. TTL-Strategien im Vergleich

Die Wahl der passenden TTL-Strategie haengt vom Lastprofil und der Kritikalitaet der Datenaktualitaet ab. Die folgende Tabelle stellt die wichtigsten Ansaetze gegenueber.

Strategie Loest Implementierungsaufwand Empfehlung
Feste TTL ohne Jitter Nichts, anfaellig fuer Thundering Herd Sehr gering Nur fuer kleine, unkritische Datenmengen
TTL-Jitter Synchrone Massenablaeufe Gering Standardloesung fuer Batch-befuellte Caches
Refresh-Ahead Cache-Miss-Spitzen bei hoher Kritikalitaet Mittel Fuer haeufig gelesene, teure Werte
Soft-TTL / Stale-While-Revalidate Direkte Backend-Last bei Cache-Miss Hoch Fuer Systeme mit strengen Latenz-Anforderungen

In der Praxis kombinieren produktive Systeme oft mehrere dieser TTL-Strategien: TTL-Jitter als Basisschutz fuer alle Batch-Writes, ergaenzt um Refresh-Ahead fuer besonders kritische, haeufig gelesene Keys. Die Kombination adressiert sowohl das Problem synchroner Massenablaeufe als auch das Problem einzelner, aber teurer Cache-Misses.

10. Zusammenfassung

Eine durchdachte Redis TTL-Strategie geht deutlich ueber das einfache Setzen von EXPIRE hinaus. Passive Expiration garantiert korrekte Antworten bei jedem Zugriff, aktive Expiration raeumt im Hintergrund ungenutzte, abgelaufene Keys auf, beide Mechanismen arbeiten Hand in Hand und nur der Master propagiert Loeschungen an Replicas. Das zentrale Risiko ohne bewusste TTL-Strategie ist der Thundering-Herd-Effekt, bei dem viele Keys gleichzeitig ablaufen und das Backend mit einer Anfragenwelle ueberfluten.

TTL-Jitter loest dieses Problem mit minimalem Aufwand, indem Ablaufzeiten ueber ein Zeitfenster gestreut werden, statt alle Keys exakt gleichzeitig ablaufen zu lassen. Fuer besonders kritische Anwendungsfaelle ergaenzen Refresh-Ahead und Soft-TTL diese Basis-Strategie um proaktive Aktualisierung und Stale-While-Revalidate-Verhalten. Wer TTL-Werte in produktiven Redis-Umgebungen setzt, sollte sich immer fragen, wie viele andere Keys dieselbe oder eine sehr aehnliche TTL erhalten, bevor ein fester Wert unreflektiert uebernommen wird.

Redis TTL-Strategien, das Wichtigste auf einen Blick

EXPIRE, TTL, PERSIST

EXPIRE setzt eine Ablaufzeit, TTL fragt die verbleibende Zeit ab, PERSIST entfernt die TTL vollstaendig.

Zwei Loeschmechanismen

Passive Expiration bei Zugriff garantiert Korrektheit, aktive Expiration raeumt periodisch im Hintergrund auf.

Thundering Herd vermeiden

Identische TTLs fuer viele Keys fuehren zu synchronem Massenablauf und ploetzlicher Backend-Last.

TTL-Jitter als Standardloesung

Ein zufaelliger Offset auf die Basis-TTL verteilt Ablaufzeiten und senkt Lastspitzen erheblich.

11. FAQ: Redis Expire und TTL-Strategien richtig einsetzen

1EXPIRE vs. PEXPIRE?
EXPIRE in Sekunden, PEXPIRE in Millisekunden fuer mehr Praezision.
2TTL -1 und -2?
-1: existiert ohne TTL, -2: Key existiert gar nicht.
3Aktiv vs. passiv?
Passiv prueft bei Zugriff, aktiv raeumt periodisch im Hintergrund auf.
4Wie oft laeuft der Zyklus?
Standardmaessig 10x pro Sekunde, adaptiv bei hoher Ablaufquote.
5Was ist Thundering Herd?
Viele Keys laufen gleichzeitig ab, Backend wird mit Anfragen ueberflutet.
6Wie funktioniert TTL-Jitter?
Zufaelliger Offset pro Key auf die Basis-TTL, verteilt Ablaufzeiten.
7Wie gross sollte der Jitter sein?
5 bis 10 Prozent der Basis-TTL als gaengiger Ausgangspunkt.
8Wozu dient PERSIST?
Entfernt die TTL vollstaendig, Key wird dauerhaft ohne Ablauf.
9Loeschen Replicas selbststaendig?
Nein, nur der Master, Replicas warten auf explizites DEL.
10Jitter vs. Refresh-Ahead?
Jitter verteilt Ablaeufe, Refresh-Ahead erneuert proaktiv vor dem Ablauf.