Top 5 Redis-Leistungsengpässe und wie man sie behebt

Entfesseln Sie die Spitzenleistung Ihrer Redis-Bereitstellungen mit diesem grundlegenden Leitfaden zu häufigen Engpässen. Lernen Sie, Probleme wie langsame O(N)-Befehle, übermäßige Netzwerk-Roundtrips, Speicherdruck und ineffiziente Räumungsrichtlinien, Persistenz-Overhead und CPU-gebundene Operationen zu identifizieren und zu beheben. Dieser Artikel bietet umsetzbare Schritte, praktische Beispiele und Best Practices – vom Einsatz von Pipelining und `SCAN` bis zur Optimierung von Datenstrukturen und Persistenz –, um sicherzustellen, dass Ihre Redis-Instanz für alle Ihre Caching-, Messaging- und Datenspeicheranforderungen schnell und zuverlässig bleibt.

Top 5 Redis-Leistungsengpässe und wie man sie behebt

Redis-Leistungsprobleme wirken meist mysteriös, bis man sich an eines erinnert: Redis ist schnell, aber nicht von Arbeit befreit. Ein Befehl, der eine Million Schlüssel durchläuft, durchläuft immer noch eine Million Schlüssel. Ein Client, der einen Befehl pro Netzwerk-Roundtrip sendet, bezahlt immer noch für jeden Roundtrip. Ein Server, dem der Speicher ausgeht, muss je nach Konfiguration immer noch räumen, auslagern, Schreibvorgänge ablehnen oder zusammenbrechen.

Wenn Redis langsamer wird, beginnen Sie nicht damit, willkürliche Einstellungen zu ändern. Beginnen Sie mit Beweisen:

redis-cli INFO
redis-cli SLOWLOG GET 20
redis-cli LATENCY DOCTOR
redis-cli INFO commandstats
redis-cli INFO memory

Diese Befehle deuten meist auf einen von fünf Engpässen hin: langsame Befehle, Netzwerk-Roundtrips, Speicherdruck, Persistenz-Overhead oder CPU-Sättigung.

1. Langsame Befehle bei großen Datenmengen

Redis hat viele winzige Operationen mit konstanter Laufzeit, aber nicht jeder Befehl ist winzig. Befehle wie KEYS, große LRANGE, SMEMBERS, HGETALL, ZRANGE über riesige Bereiche, SORT und lange Lua-Skripte können andere Clients blockieren, während sie ausgeführt werden.

Der klassische Vorfall beginnt mit einem Bereinigungs- oder Debugging-Befehl:

KEYS *

Auf einer kleinen Entwicklungsinstanz wird er sofort zurückgegeben. In einem Produktions-Keyspace mit Millionen von Schlüsseln kann er den Server lange genug anhalten, dass sich Anwendungsanfragen stauen. Das gleiche Muster tritt bei einem Hash auf, der als "ein paar Felder pro Benutzer" begann und leise zu einem riesigen Objekt wurde.

Finden Sie die Beweise:

redis-cli SLOWLOG GET 20
redis-cli INFO commandstats
redis-cli LATENCY LATEST

SLOWLOG zeichnet Befehle auf, die den konfigurierten Schwellenwert überschritten haben. INFO commandstats zeigt die Anzahl der Aufrufe pro Befehl und die kumulative Zeit. Wenn ein Befehl die Zeit dominiert, beginnen Sie dort.

Beheben Sie das Zugriffsmuster:

redis-cli --scan --pattern 'user:*'

Verwenden Sie SCAN anstelle von KEYS für die Keyspace-Iteration. Verwenden Sie HSCAN, SSCAN und ZSCAN für große Hashes, Sets und sortierte Sets. Rufen Sie Seiten oder Bereiche ab, anstatt ganze Strukturen:

LRANGE feed:user:42 0 49
ZRANGE leaderboard 0 99 WITHSCORES

Wenn ein Objekt zu groß geworden ist, teilen Sie es entsprechend der Art und Weise auf, wie die Anwendung es liest. Ein einzelner user:42-Hash mit Tausenden von nicht zusammenhängenden Feldern mag für Schreibvorgänge praktisch sein, ist aber schmerzhaft für Lesevorgänge, die nur Profileinstellungen benötigen. Separate Schlüssel wie user:42:profile, user:42:prefs und user:42:counters können die Datenmenge reduzieren, die pro Anfrage berührt wird.

Verwenden Sie zum Löschen bevorzugt UNLINK, wenn Werte groß sein können:

UNLINK old:large:set

UNLINK entfernt den Schlüssel aus dem Keyspace und gibt Speicher asynchron frei. Es ist sicherer als DEL für große Werte, obwohl die Massenbereinigung immer noch gedrosselt werden muss.

2. Zu viele Netzwerk-Roundtrips

Redis kann einen Befehl in Mikrosekunden verarbeiten, während Ihre Anwendung Millisekunden mit dem Warten auf das Netzwerk verbringt. Wenn ein Anforderungspfad 50 sequenzielle Redis-Befehle sendet, kann das Netzwerk die Gesamtzeit dominieren, selbst wenn Redis selbst gesund ist.

Dies ist üblich in Code wie:

for user_id in user_ids:
    profile = redis.get(f"user:{user_id}:profile")

Jeder GET wartet auf seine eigene Antwort, bevor der nächste beginnt. Über ein Netzwerk ist das teuer.

Verwenden Sie Pipelining:

pipe = redis.pipeline(transaction=False)
for user_id in user_ids:
    pipe.get(f"user:{user_id}:profile")
profiles = pipe.execute()

Pipelining sendet mehrere Befehle, ohne auf jede einzelne Antwort zu warten. Redis führt die Befehle weiterhin in der Reihenfolge aus, aber der Client vermeidet es, für jeden Befehl einen Roundtrip zu bezahlen.

Verwenden Sie Multi-Key-Befehle, wo sie passen:

MGET user:1:profile user:2:profile user:3:profile

Verwandeln Sie nicht jede Anfrage in eine riesige Pipeline. Große Pipelines können die Speichernutzung erhöhen und Antwortstöße erzeugen. Bündeln Sie genug, um offensichtliche Roundtrip-Verschwendung zu beseitigen, und messen Sie dann.

Überprüfen Sie bei leseintensiven Pfaden auch, ob Ihre Anwendung Redis wiederholt nach Werten fragt, die einmal abgerufen und für die Dauer einer Anfrage wiederverwendet werden könnten. Ein kleiner lokaler Anforderungscache kann versehentliche doppelte Lesevorgänge entfernen, ohne Redis zu ändern.

3. Speicherdruck und Räumungs-Churn

Redis ist speicherzentriert. Sobald der Speicher knapp wird, verschlechtert sich die Leistung auf verschiedene Weise: Räumung kostet CPU, Schreibvorgänge können unter noeviction fehlschlagen, Persistenz-Forks können schwieriger werden, Replikate können nachhinken, und das Betriebssystem kann auslagern, wenn der Host falsch konfiguriert oder überlastet ist.

Überprüfen Sie den Speicher:

redis-cli INFO memory
redis-cli INFO stats | grep evicted_keys
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

Wichtige Anzeichen:

  • used_memory ist nahe an maxmemory.
  • evicted_keys nimmt schnell zu.
  • Der Host lagert aus.
  • Große Schlüssel verbrauchen mehr Speicher als erwartet.
  • Ablaufende Cache-Schlüssel haben tatsächlich keine TTLs.

Finden Sie große Schlüssel sorgfältig. Führen Sie während der Hauptverkehrszeit keine breiten, teuren Befehle aus. Sampling mit --bigkeys kann helfen:

redis-cli --bigkeys

Setzen Sie für Caches ein Speicherlimit und eine Räumungsrichtlinie, die zu den Daten passt:

maxmemory 4gb
maxmemory-policy allkeys-lru

allkeys-lru oder allkeys-lfu kann sinnvoll sein, wenn alle Schlüssel Cache-Einträge sind. volatile-lru räumt nur Schlüssel mit TTLs, was nützlich ist, wenn persistente Schlüssel die Instanz mit Cache-Schlüsseln teilen. noeviction ist oft richtig für Redis, das als primärer Datenspeicher verwendet wird, da das stille Räumen von dauerhaften Daten schlimmer wäre als die Rückgabe eines Fehlers.

Setzen Sie TTLs beim Schreiben:

SET cache:product:123 "$json" EX 300

Seien Sie bei Sitzungsspeichern bewusst. Ein Sitzungsschlüssel ohne Ablauf ist normalerweise ein Fehler. Bei Ratenbegrenzern sollten Zähler mit dem Fenster ablaufen. Bei Streams schneiden Sie alte Einträge ab. Speicherlecks in Redis sind oft Anwendungsdaten, die nie einen Lebenszyklus erhalten haben.

Reduzieren Sie auch den Overhead von Schlüsseln und Werten, wo es darauf ankommt. Tausende winziger Schlüssel können mehr Metadaten kosten als erwartet. Manchmal ist ein kompakter Hash besser als viele einzelne Schlüssel; manchmal ist das Gegenteil der Fall, weil Lesevorgänge nur ein Feld benötigen. Messen Sie mit echten Zugriffsmustern, anstatt anzunehmen, dass eine Form immer am besten ist.

4. Persistenz- und Festplatten-E/A-Staus

Persistenz schützt Daten, führt aber zu Fork- und Festplattenverhalten, das Sie verstehen müssen. RDB-Snapshots und AOF-Neuschreibungen sind normalerweise Hintergrundoperationen, können aber dennoch Latenz durch Fork-Zeit, Copy-on-Write-Speicherdruck und Festplatten-E/A verursachen.

Überprüfen Sie den Persistenzstatus:

redis-cli INFO persistence
redis-cli LATENCY LATEST
iostat -xz 1

Achten Sie auf fehlgeschlagene Hintergrundspeicherungen, lange Fork-Zeiten, AOF-Neuschreibaktivität und Festplattensättigung. Wenn Latenzspitzen mit BGSAVE oder BGREWRITEAOF zusammenfallen, gehört die Persistenzoptimierung auf die kurze Liste.

Für AOF ist die wichtigste Einstellung für Haltbarkeit/Leistung:

appendfsync everysec

everysec ist die übliche ausgewogene Wahl. always synchronisiert jeden Schreibvorgang und kann sehr langsam sein. no überlässt die Synchronisierung dem Betriebssystem und akzeptiert ein höheres Datenverlustrisiko bei einem Absturz.

Vermeiden Sie für RDB Snapshot-Regeln, die bei einer starken Schreiblast ständig ausgelöst werden, es sei denn, das ist beabsichtigt:

save 900 1
save 300 10
save 60 10000

Diese Beispiel-Standardwerte sind nicht automatisch für jede Arbeitslast richtig. Ein Redis mit hoher Schreibrate, das als Wegwerf-Cache verwendet wird, benötigt möglicherweise überhaupt keine Persistenz. Eine Redis-Instanz, die als Job-Warteschlange oder Sitzungsspeicher verwendet wird, tut dies wahrscheinlich, aber das akzeptable Verlustfenster muss klar sein.

Wenn Persistenz mit dem Anwendungsverkehr konkurriert, ziehen Sie in Betracht:

  • Schnelleren lokalen SSD-Speicher.
  • Trennung der Redis-Persistenz von anderen festplattenintensiven Diensten.
  • Ausführen der Persistenz auf einem Replikat, wenn der Primärserver dieses Design tolerieren kann.
  • Halten der Datensatzgröße unter dem, was der Host bequem forken kann.
  • Setzen von Linux vm.overcommit_memory=1, wo Redis es für Hintergrundspeicherungen empfiehlt.

Deaktivieren Sie die Persistenz nicht blind, um "die Leistung zu verbessern", es sei denn, die Daten sind wirklich wegwerfbar. Es könnte die Grafik besser aussehen lassen, während ein Neustart zu Datenverlust wird.

5. CPU-Sättigung und single-threaded Befehlsausführung

Die Redis-Befehlsausführung ist weitgehend single-threaded, auch wenn modernes Redis zusätzliche Threads für einige E/A- und Hintergrundarbeiten verwendet. Wenn ein Kern von Redis ausgelastet ist, hilft das Hinzufügen weiterer untätiger Kerne auf derselben Instanz möglicherweise nicht dem heißen Befehlspfad.

Überprüfen Sie den Host und die Redis-Befehlsmischung:

top -H -p $(pgrep redis-server)
redis-cli INFO commandstats
redis-cli SLOWLOG GET 20
redis-cli INFO clients

Häufige CPU-Ursachen:

  • Große Set-, sortierte Set-, Listen- oder Hash-Operationen.
  • Schwere Lua-Skripte.
  • Komprimierungs- oder Serialisierungsoverhead in der Anwendung, der zu größeren Werten als erwartet führt.
  • Sehr hohe Pub/Sub-Fan-out.
  • Teure Räumung unter Speicherdruck.
  • Zu viele Verbindungen, die ständig neu verbinden oder kleine Befehle ausgeben.

Beheben Sie CPU, indem Sie die Arbeit reduzieren, die Arbeit aufteilen oder die Arbeit verteilen.

Reduzieren Sie die Arbeit durch Ändern von Befehlen und Datenformen. Wenn Sie nur 50 Elemente benötigen, holen Sie nicht 5.000. Wenn jede Anfrage ein 500 KB großes JSON-Blob parst, um ein Flag zu lesen, teilen Sie dieses Flag in einen kleineren Schlüssel oder ein kleineres Feld auf.

Teilen Sie die Arbeit auf, indem Sie lange Schleifen mit inkrementellen Scans zu Clients verschieben:

HSCAN big:hash 0 COUNT 100

Verteilen Sie die Arbeit mit Replikaten für Lesevorgänge oder Redis Cluster für Sharding. Replikate helfen bei leseintensivem Verkehr, machen aber Schreibvorgänge auf dem Primärserver nicht billiger. Redis Cluster verteilt Schlüssel auf Primärserver, was die gesamte CPU- und Speicherkapazität erhöhen kann, fügt aber auch betriebliche Komplexität und Key-Slot-Einschränkungen hinzu.

Beobachten Sie bei Pub/Sub die Ausgabepuffer und den Fan-out:

redis-cli PUBSUB NUMSUB events:updates
redis-cli CLIENT LIST

Ein langsamer Teilnehmer kann zu Speicherdruck werden. Tausende von Teilnehmern können eine einzelne Veröffentlichung in eine große Menge an Netzwerkausgabe verwandeln. Wenn Pub/Sub schwer ist, ziehen Sie in Betracht, es auf einer separaten Redis-Instanz zu isolieren.

Ein schneller Triage-Workflow

Wenn die Redis-Latenz steigt, führen Sie diese Prüfungen in der Reihenfolge durch:

redis-cli --latency
redis-cli SLOWLOG GET 10
redis-cli LATENCY DOCTOR
redis-cli INFO memory
redis-cli INFO clients
redis-cli INFO persistence
redis-cli INFO commandstats

Fragen Sie dann:

  • Ist ein langsamer Befehl aufgetreten?
  • Hat der Speicher maxmemory erreicht oder mit der Räumung begonnen?
  • Hat die Persistenz eine Speicherung oder Neuschreibung gestartet?
  • Sind die verbundenen Clients oder blockierten Clients gesprungen?
  • Ist die Anzahl der Aufrufe oder die Zeit eines Befehls explodiert?
  • Haben Anwendungsbereitstellungen die Redis-Zugriffsmuster geändert?

Die meisten Redis-Engpässe werden nicht durch eine magische Einstellung gelöst. Sie werden gelöst, indem die Arbeitslast kleiner, inkrementeller, stärker gebündelt oder besser isoliert wird. Die besten Redis-Bereitstellungen sind langweilig: Schlüssel laufen ab, wenn sie sollten, große Operationen werden paginiert, Clients pipelinen weise, Persistenz-Einstellungen entsprechen dem Wert der Daten, und die Überwachung erfasst den Trend, bevor Benutzer ihn spüren.

Was vor und nach einer Korrektur gemessen werden sollte

Eine Leistungskorrektur ist nur real, wenn sich das Diagramm für die relevante Arbeitslast ändert. Bevor Sie Code oder Konfiguration ändern, erfassen Sie eine kleine Basislinie:

redis-cli INFO stats
redis-cli INFO commandstats
redis-cli INFO memory
redis-cli INFO persistence
redis-cli SLOWLOG GET 20

Erfassen Sie auf Systemebene CPU, Festplatte, Speicher, Auslagerung und Netzwerkdurchsatz. Wenn Redis in einem Container läuft, überprüfen Sie sowohl die Container-Limits als auch den Host-Druck. Ein Redis-Prozess kann in seiner eigenen Speicheransicht gut aussehen, während der Host unter Festplatten- oder CPU-Druck von einem anderen Dienst leidet.

Vergleichen Sie nach der Änderung:

  • p50, p95 und p99 Anwendungslatenz für Redis-gestützte Anfragen.
  • Redis-Befehls-Latenz, nicht nur Anforderungslatenz.
  • Slowlog-Einträge nach Befehl.
  • Räumungsrate und Speicherreserve.
  • Verbundene Clients und abgelehnte Verbindungen.
  • Persistenz-Fork-Zeit und AOF/RDB-Status.
  • Replikatverzögerung, wenn Replikate Lesevorgänge bedienen oder die Haltbarkeit schützen.

Seien Sie misstrauisch gegenüber Korrekturen, die den Schmerz nur verschieben. Beispielsweise kann eine große Pipeline die Anforderungslatenz reduzieren, aber Speicherspitzen erhöhen. Das Deaktivieren von AOF kann die Festplattenlatenz beseitigen, aber die Wiederherstellung schwächen. Das Erhöhen von maxmemory kann Räumungen verzögern, aber den Host aushungern, wenn die Maschine bereits gemeinsam genutzt wurde.

Eine nützliche Praxis ist es, einen kleinen Auslastungstest um das genaue Redis-Muster zu schreiben, das Sie geändert haben. Wenn der alte Code 40 sequenzielle GETs gemacht hat, testen Sie sequenzielle GETs gegen MGET oder Pipelining mit realistischen Nutzlastgrößen. Wenn der alte Code HGETALL verwendet hat, testen Sie HGET für die Felder, die von der Anfrage tatsächlich benötigt werden. Redis-Tuning ist viel einfacher, wenn Sie die Form benchmarken, die Sie tatsächlich ausführen, nicht eine generische "Redis-Operationen pro Sekunde"-Zahl.

Halten Sie schließlich den Rollback einfach. Eine Redis-Leistungsänderung sitzt oft gleichzeitig im Anwendungscode, in den Client-Einstellungen und in der Serverkonfiguration. Ändern Sie nach Möglichkeit eine Sache. Wenn Sie mehrere Dinge ändern müssen, notieren Sie, welches Symptom jede Änderung verbessern soll. Das verhindert, dass der nächste Entwickler einen Haufen mysteriöser Einstellungen erbt, die niemand entfernen möchte.