Eine Abfrage, die 200ms gegen Ihre Datenbank braucht, braucht auch beim millionsten Aufruf noch 200ms, und jeder dieser Aufrufe konkurriert um dieselben Verbindungen, dieselbe Disk-I/O und dieselbe CPU. Redis hält die Antwort im Speicher, auf einem Server, der sonst nichts tut, sodass der millionste Lesevorgang einen Netzwerk-Roundtrip kostet statt eines Query-Plans.
Was Redis als Cache wirklich macht
Redis ist ein Key-Value-Store im Arbeitsspeicher. Werte liegen im RAM, daher liegen Lese- und Schreibzeiten im Sub-Millisekunden-Bereich statt bei den zehn bis hundert Millisekunden, die eine relationale Abfrage kostet, sobald Joins, belastete Indizes oder ein ausgelasteter Connection-Pool ins Spiel kommen. Redis steht neben Ihrer Datenbank, nicht an ihrer Stelle: Ihre Anwendung fragt zuerst Redis und geht nur dann zur Datenbank, wenn Redis die Antwort nicht hat.
Das Muster dafĂĽr heiĂźt Cache-Aside (auch “Lazy Loading”), und es ist das erste, zu dem die meisten Anwendungen greifen:
- Die Anwendung fragt Redis nach einem Key.
- Cache Hit — Redis hat ihn, gibt ihn zurück. Die Datenbank bekommt die Anfrage gar nicht zu sehen.
- Cache Miss — Redis hat ihn nicht. Fragt die Datenbank ab, schreibt das Ergebnis in Redis und gibt es dann zurück.
| |
Die Datenbank bleibt die Quelle der Wahrheit. Redis hält eine schnelle, wegwerfbare Kopie der Teile, die Sie häufig lesen. Startet Redis leer neu, geht nichts verloren: Es füllt sich bei der nächsten Welle von Cache Misses von selbst wieder auf.
Wie Sie Redis mit Docker starten
Der schnellste Weg zu einer Redis-Instanz ist das offizielle Image. Wenn Sie Docker noch nie benutzt haben, deckt Was ist Docker und wofür wird es verwendet zunächst die Grundlagen ab.
| |
Das startet Redis 8 im Hintergrund und veröffentlicht Port 6379 auf Ihrer Maschine. Verbinden Sie sich mit redis-cli, entweder lokal installiert oder aus einem zweiten Container im selben Netzwerk gestartet:
| |
| |
Gibt TTL -1 zurück, hat der Key keine Ablaufzeit: Er bleibt bestehen, bis Sie ihn löschen oder Redis ihn unter Speicherdruck entfernt. Für einen Cache wollen Sie fast immer eine Ablaufzeit, damit veraltete Daten von selbst verfallen.
| |
EX 300 lässt den Key in 300 Sekunden ablaufen. PX macht dasselbe in Millisekunden, und EXPIRE user:42:name 300 setzt ein TTL auf einen bereits existierenden Key.
Für alles, was über eine schnelle manuelle Prüfung hinausgeht, betreiben Sie Redis über Compose zusammen mit dem Rest Ihres Stacks — siehe Was Ist Docker Compose und Wie Wird Es Verwendet:
| |
api erreicht Redis über den Hostnamen redis, weil Compose beide Dienste in dasselbe benutzerdefinierte Netzwerk stellt — der Mechanismus aus Was ist ein Docker-Netzwerk und wie wird es verwendet. Das Volume redis-data ist für einen reinen Cache weniger wichtig als für eine primäre Datenbank (siehe Was ist ein Docker-Volume und wie wird es verwendet): Verlieren Sie den Cache, bedeutet das nur, dass die nächsten Lesevorgänge zu Cache Misses werden. Behalten Sie es trotzdem, wenn ein bereits warmer Cache nach einem Neustart für Ihr Latenzbudget zählt.
Wie Sie Cache-Aside-Logik in Ihrer Anwendung schreiben
Redis gibt Ihnen die Befehle; Ihre Anwendung muss sie trotzdem in der richtigen Reihenfolge aufrufen. Eine Datenbankabfrage in Cache-Aside einzupacken sieht so aus:
| |
Zwei Details erledigen hier den GroĂźteil der Arbeit:
- Das TTL (
ex=300) ist nicht optional. Ohne es liefert eine Zeile, die sich in der Datenbank ändert, für immer ihren alten Wert aus Redis. Richten Sie das TTL danach aus, wie lange veraltete Daten für diesen konkreten Key akzeptabel sind, nicht nach einer pauschalen Zahl für die ganze Anwendung. json.dumps/json.loads. Redis speichert Strings, dazu eine Handvoll reichhaltigerer Typen wie Hashes und Sorted Sets — niemals die Objekte Ihrer Sprache. Alles Strukturierte muss beim Schreiben serialisiert und beim Lesen wieder geparst werden.
Ändert sich die zugrunde liegende Zeile, löschen Sie den Key, statt auf das TTL zu warten:
| |
Der nächste Lesevorgang nach einem Update ist ein garantierter Cache Miss, der Redis mit der frischen Zeile wieder auffüllt. Cache-Aside versucht nie, den Cache in Echtzeit synchron zu halten. Es lässt veraltete Einträge schnell verschwinden, per TTL oder durch explizites Löschen beim Schreiben.
Eviction: Was passiert, wenn Redis der Speicher ausgeht
Redis hält alles im RAM, daher ist der Speicher begrenzt — anders, als es bei einer Festplatte meist der Fall ist. Setzen Sie mit maxmemory eine harte Obergrenze, und sagen Sie Redis mit maxmemory-policy, was zu tun ist, wenn diese Grenze erreicht ist:
| Policy | Verhalten | Geeignet fĂĽr |
|---|---|---|
noeviction | Verweigert neue Schreibvorgänge, sobald voll, gibt einen Fehler zurück | Einen Datastore, der keine Daten stillschweigend verlieren darf |
allkeys-lru | Entfernt den am längsten nicht genutzten Key, egal welchen | Einen reinen Cache — die vernünftige Standardwahl |
volatile-lru | Entfernt den am längsten nicht genutzten Key unter denen mit TTL | Cache-Keys und dauerhafte Keys in einer Instanz mischen |
volatile-ttl | Entfernt zuerst den Key mit der kĂĽrzesten verbleibenden TTL | Rate-Limiter, kurzlebige Tokens |
FĂĽr eine dedizierte Cache-Instanz ist allkeys-lru fast immer richtig: Jeder Key darin ist per Definition wegwerfbar, also lassen Sie Redis verwerfen, was Sie am längsten nicht angefasst haben. noeviction ist die falsche Policy fĂĽr einen Cache, weil sie “Redis ist voll” in einen Anwendungsfehler verwandelt, statt kalte Einträge still fallenzulassen.
| |
Setzen Sie beides im Startbefehl, wie im Compose-File oben, nicht nur zur Laufzeit. Ein Container-Neustart setzt ein nur zur Laufzeit gesetztes CONFIG SET auf die Redis-Standardwerte zurĂĽck (maxmemory 0, also unbegrenzt, nur durch den Host gedeckelt).
Wann Redis-Caching die falsche Lösung ist
Caching versteckt einen langsamen Pfad; es repariert ihn nicht. Vier Situationen, in denen es die Sache schlimmer macht, wenn Sie zuerst zu Redis greifen:
- Die Daten ändern sich bei jedem Lesevorgang ohnehin. Ein Live-Aktienkurs oder ein Zähler, der bei jeder Anfrage hochgezählt wird, gewinnt nichts durch einen Cache, der so schnell invalidiert wird, wie er gefüllt ist. Sie haben einen Netzwerk-Hop hinzugefügt, ohne Hit-Rate zu gewinnen.
- Sie brauchen starke Konsistenz. Cache-Aside ist per Design eventually consistent: Zwischen einem Schreibvorgang und dem nächsten Cache Miss gibt es ein Fenster, in dem noch ein veralteter Wert ausgeliefert werden kann. Für einen Kontostand oder einen Lagerbestand, bei denen dieses Fenster echten Schaden anrichtet, cachen Sie ihn nicht, oder cachen Sie ihn mit einem TTL von wenigen Sekunden und akzeptieren Sie den Kompromiss bewusst.
- Der eigentliche Engpass liegt woanders. Ist Ihre Datenbank wegen eines fehlenden Index oder einer N+1-Abfrage langsam, beheben Sie das zuerst. Eine schlecht geschriebene Abfrage zu cachen verschiebt nur dieselbe falsche Antwort schneller in den Speicher.
- Ein Thundering Herd beim Ablauf. Läuft ein heißer Key ab, können alle gleichzeitigen Anfragen auf einmal den Cache verfehlen und die Datenbank gleichzeitig bombardieren. Nutzen Sie
SET key value NX EX 30als kurzlebige Sperre, gehalten von der Anfrage, die den Cache neu befĂĽllt, damit die ĂĽbrigen darauf warten, statt sich alle auf die Datenbank zu stĂĽrzen.
Womit Sie beim Redis-Caching anfangen sollten
Stellen Sie einen redis:8-Container vor Ihren langsamsten, am häufigsten wiederholten Lesevorgang. Packen Sie ihn in Cache-Aside mit einem TTL, das Sie begründen können, nicht mit einer zufällig gewählten runden Zahl, und setzen Sie maxmemory-policy allkeys-lru, damit ein voller Cache degradiert statt Fehler zu werfen. Messen Sie danach die Hit-Rate. Write-Through-Caching, Invalidierung per Pub/Sub und Redis Streams kosten alle Komplexität, und keines davon lohnt sich, bevor ein einfaches Cache-Aside-Setup Ihnen nicht genau gezeigt hat, wo es an seine Grenzen stößt.