Redis-Speicherverwaltung für Spitzenleistung meistern

Entfesseln Sie die Spitzenleistung von Redis, indem Sie Techniken zur Speicherverwaltung meistern. Dieser umfassende Leitfaden behandelt entscheidende Aspekte wie das Verständnis des Redis-Speicherfußabdrucks, die Überwachung mit `INFO memory` und `MEMORY USAGE` sowie die Optimierung von Datenstrukturen. Lernen Sie, Fragmentierung mit aktiver Defragmentierung zu bekämpfen, effiziente Räumungsrichtlinien (`maxmemory`, `allkeys-lru`) zu konfigurieren und Lazy Freeing für reibungslosere Abläufe zu nutzen. Setzen Sie diese umsetzbaren Strategien um, um den Redis-Durchsatz zu steigern, die Latenz zu reduzieren und ein stabiles, leistungsstarkes Caching und Datenspeicherung zu gewährleisten.

Redis-Speicherverwaltung für Spitzenleistung meistern

Redis ist schnell, weil es Daten im Speicher hält, aber dieses Design führt dazu, dass Speicherfehler schnell sichtbar werden. Ein Cache ohne Ablaufzeiten wächst, bis Schreibvorgänge fehlschlagen. Einige wenige große Schlüssel erzeugen Latenzspitzen, wenn sie gelöscht werden. Ein Hintergrundspeichervorgang kann mehr Speicher benötigen als erwartet, aufgrund von Copy-on-Write. Ein langsamer Client kann einen Ausgabepuffer aufbauen, der groß genug ist, um Teil des Problems zu werden.

Gute Redis-Speicherverwaltung bedeutet nicht nur "mehr RAM kaufen". Es geht darum zu wissen, was gespeichert ist, wie lange es leben soll, was an der Speichergrenze passiert und welche Operationen den Server blockieren können, wenn Schlüssel groß sind.

Redis-Speichernutzung verstehen

Redis nutzt Systemspeicher, um alle seine Daten zu speichern. Wenn Sie einen Schlüssel-Wert mit SET setzen, weist Redis Speicher für sowohl den Schlüsselstring als auch den Wert zu, zusammen mit etwas Overhead für interne Datenstrukturen. Das Verständnis der verschiedenen Komponenten der Speichernutzung ist der erste Schritt zu einer effektiven Verwaltung:

  • Datenspeicher: Dies ist der Speicher, der von Ihren tatsächlichen Daten verbraucht wird (Schlüssel, Werte und interne Datenstrukturen wie Wörterbücher zur Zuordnung von Schlüsseln zu Werten). Die Größe hängt von der Anzahl und Größe Ihrer Schlüssel und Werte sowie den von Ihnen gewählten Datenstrukturen ab (Strings, Hashes, Listen, Sets, Sortierte Sets).
  • Overhead-Speicher: Redis fügt für jeden Schlüssel Overhead hinzu, einschließlich Zeigern, Metadaten, Ablaufinformationen und räumungsbezogenen Daten. Kleine aggregierte Strukturen können kompakte Kodierungen wie Listpack oder Intset verwenden, abhängig von der Redis-Version und dem Datentyp, während größere Strukturen allgemeinere Darstellungen verwenden.
  • Pufferspeicher: Redis verwendet Client-Ausgabepuffer, Replikations-Backlog-Puffer und AOF-Puffer. Große oder langsame Clients oder ein ausgelastetes Replikations-Setup können erheblichen Pufferspeicher verbrauchen.
  • Fork-Speicher: Wenn Redis Hintergrundoperationen wie das Speichern von RDB-Snapshots oder das Umschreiben von AOF-Dateien durchführt, erstellt es einen untergeordneten Prozess mit fork. Dieser untergeordnete Prozess teilt sich zunächst den Speicher mit dem übergeordneten Prozess über Copy-on-Write (CoW). Jegliche Schreibvorgänge in den Datensatz durch den übergeordneten Prozess nach dem fork führen jedoch dazu, dass Seiten dupliziert werden, was den gesamten Speicherfußabdruck erhöht.

Redis-Speicher überwachen

Die regelmäßige Überwachung des Redis-Speichers ist entscheidend, um potenzielle Probleme zu erkennen, bevor sie eskalieren. Das wichtigste Werkzeug dafür ist der Befehl INFO memory, zusammen mit MEMORY USAGE.

Der Befehl INFO memory

redis-cli INFO memory

Wichtige Metriken aus INFO memory:

  • used_memory: Die Gesamtzahl der Bytes, die von Redis mit seinem Allokator (jemalloc, glibc usw.) zugewiesen wurden. Dies ist die Summe des Speichers, der von Ihren Daten, internen Datenstrukturen und temporären Puffern verwendet wird.
  • used_memory_human: used_memory in menschenlesbarem Format.
  • used_memory_rss: Resident Set Size (RSS), die Menge an Speicher, die vom Redis-Prozess verbraucht wird, wie vom Betriebssystem gemeldet. Dies umfasst Redis' eigene Zuweisungen sowie Speicher, der von der Speicherverwaltung des Betriebssystems, gemeinsam genutzten Bibliotheken und möglicherweise fragmentiertem Speicher, der noch nicht an das Betriebssystem zurückgegeben wurde, verwendet wird.
  • mem_fragmentation_ratio: Dies ist ungefähr used_memory_rss / used_memory. Ein Wert über 1,0 ist normal. Ein viel höherer Wert kann auf Fragmentierung, Allokatorverhalten oder RSS hinweisen, das nicht an das Betriebssystem zurückgegeben wurde. Ein Wert unter 1,0 ist ein Warnsignal, das es zu untersuchen gilt, da er auf ausgelagerten Speicher oder zeitliche Messungseffekte hindeuten kann.
  • allocator_frag_bytes: Bytes der Fragmentierung, die vom Speicherallokator gemeldet werden.
  • lazyfree_pending_objects: Anzahl der Objekte, die darauf warten, asynchron freigegeben zu werden.

Der Befehl MEMORY USAGE

Um die Speichernutzung einzelner Schlüssel zu überprüfen:

redis-cli MEMORY USAGE mykey
redis-cli MEMORY USAGE myhashkey SAMPLES 0 # Schätzung für Aggregate

Dieser Befehl liefert eine geschätzte Speichernutzung für einen bestimmten Schlüssel und hilft Ihnen, große oder ineffizient gespeicherte Datenpunkte zu identifizieren.

Wichtige Strategien zur Speicheroptimierung

Die Optimierung des Speichers in Redis umfasst mehrere proaktive Schritte, von der Wahl der richtigen Datentypen bis zur Verwaltung der Fragmentierung.

1. Optimierung der Datenstruktur

Redis bietet mehrere Datenstrukturen, jede mit ihrem eigenen Speicherverhalten. Die richtige Struktur hängt davon ab, wie die Anwendung die Daten liest und schreibt.

  • Strings: Am einfachsten, aber achten Sie auf große Strings. Die Verwendung von SET oder GET bei sehr großen Strings (MB) kann die Leistung aufgrund von Netzwerk- und Speicherübertragungs-Overhead beeinträchtigen.
  • Hashes, Listen, Sets, Sortierte Sets (Aggregate): Redis kann kleine aggregierte Datentypen kompakt kodieren. Die genauen Kodierungsnamen und Schwellenwerte variieren je nach Redis-Version. Überprüfen Sie daher Ihre redis.conf und die Ausgabe von OBJECT ENCODING, anstatt alte ziplist-Terminologie überall anzunehmen.
    • Tipp: Halten Sie einzelne aggregierte Mitglieder klein. Bei Hashes bevorzugen Sie viele kleine Felder gegenüber wenigen großen.
    • Konfiguration: Die Redis-Versionen unterscheiden sich hier. Ältere Versionen verwendeten *-ziplist-*-Einstellungen; neuere Versionen verwenden häufig *-listpack-*-Einstellungen für einige Strukturen. Passen Sie diese sorgfältig an und testen Sie mit echten Daten, da kompakte Kodierungen Speicher sparen, aber für bestimmte Zugriffsmuster CPU kosten können.

2. Best Practices für das Schlüsseldesign

Während Werte typischerweise mehr Speicher verbrauchen, ist auch die Optimierung von Schlüsselnamen wichtig:

  • Kurze, beschreibende Schlüssel: Kürzere Schlüssel sparen Speicher, insbesondere wenn Sie Millionen davon haben. Opfern Sie jedoch nicht die Klarheit für extreme Kürze. Streben Sie nach beschreibenden, aber dennoch prägnanten Schlüsselnamen.
    • Schlecht: user:1000:profile:details:email
    • Gut: user:1000:email (wenn Sie nur die E-Mail speichern)
  • Präfixe: Verwenden Sie konsistente Präfixe (z. B. user:, product:) zu Organisationszwecken. Dies hat minimale Auswirkungen auf den Speicher, erleichtert aber die Verwaltung.

3. Minimierung des Overheads

Jeder Schlüssel und Wert hat einen gewissen internen Overhead. Die Reduzierung der Anzahl der Schlüssel, insbesondere kleiner, kann effektiv sein.

  • Hash anstelle mehrerer Strings: Wenn Sie viele zusammengehörige Felder für eine Entität haben, speichern Sie sie in einem einzigen HASH anstelle mehrerer STRING-Schlüssel. Dies reduziert die Anzahl der Schlüssel der obersten Ebene und deren zugehörigen Overhead.
    • Beispiel: Verwenden Sie anstelle von user:1:name, user:1:email, user:1:age einen HASH-Schlüssel user:1 mit den Feldern name, email, age.

4. Verwaltung der Speicherfragmentierung

Speicherfragmentierung tritt auf, wenn der Speicherallokator keine zusammenhängenden Speicherblöcke in der exakt benötigten Größe finden kann, was zu ungenutzten Lücken führt. Dies kann dazu führen, dass used_memory_rss deutlich höher ist als used_memory.

  • Ursachen: Häufiges Einfügen und Löschen von Schlüsseln unterschiedlicher Größe, insbesondere nachdem der Speicherallokator lange gelaufen ist.
  • Erkennung: Ein mem_fragmentation_ratio deutlich über 1,0 (z. B. 1,5-2,0) weist auf eine hohe Fragmentierung hin.
  • Lösungen:
    • Redis 4.0+ Aktive Defragmentierung: Redis kann Speicher ohne Neustart aktiv defragmentieren. Aktivieren Sie es mit activedefrag yes in redis.conf und konfigurieren Sie active-defrag-max-scan-time und active-defrag-cycle-min/max. Dadurch kann Redis Daten verschieben und Speicher kompaktieren.
    • Redis neu starten: Die einfachste, wenn auch störende Methode zur Defragmentierung des Speichers ist der Neustart des Redis-Servers. Dies gibt den gesamten Speicher an das Betriebssystem zurück, und der Allokator startet neu. Stellen Sie bei persistenten Instanzen sicher, dass vor dem Neustart ein RDB-Snapshot oder eine AOF-Datei gespeichert wird.
# redis.conf Einstellungen für aktive Defragmentierung
activedefrag yes
active-defrag-ignore-bytes 100mb  # Nicht defragmentieren, wenn Fragmentierung kleiner als 100 MB ist
active-defrag-threshold-lower 10  # Defragmentierung starten, wenn Fragmentierungsverhältnis > 10 %
active-defrag-threshold-upper 100 # Defragmentierung stoppen, wenn Fragmentierungsverhältnis > 100 %
active-defrag-cycle-min 1         # Minimale CPU-Auslastung für Defragmentierung (1-100 %)
active-defrag-cycle-max 20        # Maximale CPU-Auslastung für Defragmentierung (1-100 %)

Räumungsrichtlinien: Verwalten von maxmemory

Wenn Redis als Cache verwendet wird, ist es entscheidend zu definieren, was passiert, wenn der Speicher eine vordefinierte Grenze erreicht. Die Direktive maxmemory in redis.conf setzt diese Grenze, und maxmemory-policy bestimmt die Räumungsstrategie.

maxmemory 2gb # Maximalen Speicher auf 2 Gigabyte setzen
maxmemory-policy allkeys-lru # Am wenigsten kürzlich verwendete Schlüssel aus allen Schlüsseln räumen

Häufige maxmemory-policy-Optionen:

  • noeviction: (Standard) Neue Schreibvorgänge werden blockiert, wenn maxmemory erreicht ist. Lesevorgänge funktionieren weiterhin. Dies ist gut zum Debuggen, aber typischerweise nicht für Produktions-Caches geeignet.
  • allkeys-lru: Räumt die am wenigsten kürzlich verwendeten (LRU) Schlüssel aus allen Keyspaces (Schlüssel mit oder ohne Ablauf).
  • volatile-lru: Räumt LRU-Schlüssel aus nur den Schlüsseln, die eine Ablaufzeit haben.
  • allkeys-lfu: Räumt die am wenigsten häufig verwendeten (LFU) Schlüssel aus allen Keyspaces.
  • volatile-lfu: Räumt LFU-Schlüssel aus nur den Schlüsseln, die eine Ablaufzeit haben.
  • allkeys-random: Räumt zufällig Schlüssel aus allen Keyspaces.
  • volatile-random: Räumt zufällig Schlüssel aus nur den Schlüsseln, die eine Ablaufzeit haben.
  • volatile-ttl: Räumt Schlüssel mit der kürzesten Time To Live (TTL) aus nur den Schlüsseln, die eine Ablaufzeit haben.

Die richtige Richtlinie wählen:

  • Für allgemeines Caching sind allkeys-lru oder allkeys-lfu oft gute Wahl, je nachdem, ob Aktualität oder Häufigkeit ein besserer Indikator für die Nützlichkeit Ihrer Daten ist.
  • Wenn Sie Redis hauptsächlich für Sitzungsverwaltung oder Objekte mit expliziten Ablaufzeiten verwenden, könnten volatile-lru oder volatile-ttl besser geeignet sein.

Warnung: Wenn maxmemory-policy auf noeviction gesetzt ist und maxmemory erreicht wird, schlagen Schreibvorgänge fehl, was zu Anwendungsfehlern führt.

Auswahl eines maxmemory-Werts

Setzen Sie maxmemory nicht gleich dem gesamten Server-RAM. Lassen Sie Platz für das Betriebssystem, den Redis-Prozess-Overhead, Client-Puffer, Replikations-Backlog, Persistenz-Copy-on-Write, Überwachungsagenten und Notfall-SSH-Zugriff.

Für eine reine Cache-Redis-Instanz ist ein einfacher Ausgangspunkt, maxmemory mit einem komfortablen Abstand unter den physischen RAM zu setzen und dann die tatsächlichen Metriken während Spitzenlast und Hintergrundpersistenz zu beobachten. Für eine persistenzintensive Instanz lassen Sie mehr Spielraum, da RDB-Snapshots und AOF-Umschreibungen den Speicherdruck vorübergehend erhöhen können.

Die gefährliche Konfiguration ist kein Limit auf einem gemeinsam genutzten Host. Redis kann dann mit dem Betriebssystem und anderen Diensten konkurrieren, bis der Kernel-OOM-Killer entscheidet, was stirbt. Ein klares maxmemory plus eine durchdachte Räumungsrichtlinie ist einfacher zu handhaben.

Große Schlüssel sind auch ein Latenzproblem

Speicherverwaltung betrifft nicht nur die Gesamtbytes. Ein einziger großer Schlüssel kann Operationen beeinträchtigen, die harmlos aussehen.

Beispiele:

redis-cli MEMORY USAGE huge:hash
redis-cli HLEN huge:hash
redis-cli LLEN queue:events

Das Löschen eines sehr großen Schlüssels mit DEL kann die Ereignisschleife blockieren, während Redis Speicher freigibt. Bevorzugen Sie UNLINK für große Schlüssel, wenn Ihre Redis-Version dies unterstützt:

redis-cli UNLINK huge:hash

UNLINK trennt den Schlüssel und gibt Speicher asynchron frei. Der Schlüssel verschwindet schnell aus dem Keyspace, während die teure Freigabe im Hintergrund erfolgt.

Bei großen Sammlungen sollten Sie auf begrenzte Größen achten. Trimmen Sie Streams und Listen. Teilen Sie große Hashes nach Mandant, Zeitbucket oder Objekttyp auf, wenn dies dem Zugriffsmuster entspricht. Ein Hash mit einer Million Feldern mag etwas Overhead auf oberster Ebene sparen, kann aber umständlich zu migrieren, ablaufen zu lassen, zu überprüfen oder zu löschen sein.

Persistenz und Speicher-Overhead

Die Persistenzmechanismen von Redis (RDB und AOF) interagieren ebenfalls mit dem Speicher:

  • RDB-Snapshots: Wenn Redis eine RDB-Datei speichert, erstellt es einen untergeordneten Prozess mit fork. Während des Snapshot-Prozesses führen alle Schreibvorgänge des übergeordneten Prozesses in den Redis-Datensatz dazu, dass Speicherseiten aufgrund von Copy-on-Write (CoW) dupliziert werden. Dies kann den Speicherfußabdruck vorübergehend verdoppeln, insbesondere bei ausgelasteten Instanzen mit häufigen RDB-Speicherungen.
  • AOF-Umschreibung: Ähnlich verhält es sich, wenn die AOF-Datei umgeschrieben wird (z. B. BGREWRITEAOF), erfolgt ein fork, was zu einer vorübergehenden Speicherverdopplung führt. Der AOF-Puffer selbst verbraucht ebenfalls Speicher.

Tipp: Planen Sie RDB-Speicherungen und AOF-Umschreibungen nach Möglichkeit außerhalb der Hauptgeschäftszeiten oder stellen Sie sicher, dass Ihr Server über ausreichend freien RAM verfügt, um den CoW-Overhead zu bewältigen.

Lazy Freeing

Redis 4.0 führte Lazy Freeing (nicht blockierende Löschung) ein, um zu verhindern, dass der Server beim Löschen großer Schlüssel oder beim Leeren von Datenbanken blockiert wird. Anstatt synchron Speicher freizugeben, kann Redis die Aufgabe der Speicherfreigabe in einen Hintergrundthread verschieben.

  • lazyfree-lazy-eviction yes: Gibt Speicher asynchron während der Räumung frei.
  • lazyfree-lazy-expire yes: Gibt Speicher asynchron frei, wenn Schlüssel ablaufen.
  • lazyfree-lazy-server-del yes: Gibt Speicher asynchron für serverseitige Löschungen in unterstützten Pfaden frei.
  • lazyfree-lazy-user-del yes: Bewirkt, dass sich benutzerinitiiertes DEL auf unterstützten Redis-Versionen eher wie UNLINK verhält.

Aktivieren Sie Lazy Freeing bei ausgelasteten Instanzen sorgfältig, um Latenzspitzen durch synchrone Speicherfreigabe zu reduzieren. Beobachten Sie dann lazyfree_pending_objects; bleibt es hoch, kann die Hintergrundfreigabe nicht mithalten.

Client-Puffer und Pipelining

Pipelining, obwohl in erster Linie eine Netzwerkoptimierungstechnik, kann die Speicherleistung indirekt beeinflussen, indem es die Befehlsverarbeitung effizienter macht. Durch das Senden mehrerer Befehle an Redis in einer einzigen Hin- und Rückrunde werden Netzwerklatenz und CPU-Overhead pro Befehl sowohl auf Client- als auch auf Serverseite reduziert. Dies ermöglicht Redis, mehr Operationen pro Sekunde zu verarbeiten, ohne große Befehlswarteschlangen aufzubauen, die sonst zu höherer Speichernutzung in Client-Puffern oder langsamerer Verarbeitung führen könnten, die den Speicherallokator im Laufe der Zeit belastet.

Pipelining kann den Durchsatz verbessern, aber unbegrenzte Pipelines können die Client-Ausgabepuffer vergrößern. Ein Client, der eine riesige Pipeline sendet und Antworten langsam liest, kann dazu führen, dass Redis eine große Menge an ausstehenden Ausgaben hält.

Beobachten Sie die Client-Puffer-Metriken in INFO clients und konfigurieren Sie sinnvolle Grenzen für normale, Replikat- und Pub/Sub-Clients:

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

Dies sind beispielhafte Standardwerte, keine universellen Empfehlungen. Der Schlüssel ist, unbegrenztes Wachstum für Clients zu vermeiden, die zurückfallen können.

Eine nützliche Routine zur Speicherüberprüfung

Wenn eine Redis-Instanz mehr Speicher als erwartet verbraucht, gehen Sie vom Allgemeinen zum Spezifischen vor:

  1. Überprüfen Sie INFO memory auf used_memory, RSS, Fragmentierung, Allokatorstatistiken und ausstehende Lazy Frees.
  2. Überprüfen Sie INFO keyspace, um zu sehen, ob eine Datenbank oder Schlüsselfamilie wächst.
  3. Stichprobenartig große Schlüssel mit MEMORY USAGE, SCAN und typspezifischen Längenbefehlen überprüfen.
  4. Bestätigen Sie, dass cacheähnliche Schlüssel TTLs haben.
  5. Überprüfen Sie kürzliche Bereitstellungen auf neue Schlüsselnamen, längere Werte oder fehlende Ablaufzeiten.
  6. Überprüfen Sie das Timing der Persistenz und den fork-bedingten Speicherdruck.
  7. Überprüfen Sie Client-Puffer, wenn der Speicher während Verkehrsspitzen ansteigt.

Wenn beispielsweise das Speicherwachstum auf eine neue Feature-Veröffentlichung folgt, suchen Sie nach neuen Schlüsseln ohne Ablauf:

redis-cli --scan --pattern 'feature-x:*' | head
redis-cli TTL feature-x:example

Eine TTL von -1 bedeutet, dass der Schlüssel keine Ablaufzeit hat. Das kann für dauerhafte Daten korrekt sein. Für Cachedaten ist es normalerweise falsch.

Redis-Speicherprobleme lassen sich am einfachsten beheben, bevor die Instanz voll ist. Setzen Sie ein bewusstes maxmemory, wählen Sie eine Räumungsrichtlinie, die der Rolle der Instanz entspricht, halten Sie Cache-Schlüssel auf TTLs, vermeiden Sie übermäßig große Schlüssel und lassen Sie Spielraum für Forks und Puffer. Überprüfen Sie dann die tatsächlichen Speichermetriken nach jeder größeren Änderung der Datenform. Redis bleibt schnell, wenn sein Speicherverhalten langweilig ist.