Redis über VPN fühlt sich langsam an: weniger Roundtrips pro Anfrage
Ein VPN macht aus einem entfernten Redis-Aufruf keinen lokalen. Kalkulieren Sie Roundtrips pro Anfrage, nutzen Sie Verbindungen wieder und halten Sie Pipelines von Transaktionen fern.
Eine Seite, die gegen ein lokales Redis in 200 ms antwortet, braucht vier Sekunden, sobald der Client hinter einem VPN sitzt, während die Serverzähler stillstehen. Das Slow Log zeigt nichts über einer Millisekunde, der CPU-Graph ist flach, und die Instanz bedient andere Clients ohne Beschwerden. Was sich geändert hat, ist die Entfernung, und was die Entfernung vervielfacht, ist die Anzahl der Male, in denen Ihr Code anhält und wartet. Gehen Sie den Anfragepfad vom Client nach außen und die Lösung sind fast immer weniger sequenzielle Wartezeiten statt eines schnelleren Servers.
Fangen Sie beim Client an, nicht bei den Servermetriken
Beobachten Sie den Clientprozess zwischen dem ersten und dem letzten Befehl. Zeigt der Trace eine Antwort, eine Pause, dann den nächsten Befehl, zahlen Sie einen vollen Roundtrip pro Befehl, und keine Serverabstimmung ändert das. Das Slow Log ist hier das falsche Instrument, denn es erfasst Ausführungszeit und schließt Kommunikations-I/O bewusst aus. Ein sauberes Slow Log neben einem langsamen Endpunkt ist die Signatur eines Roundtrip-Problems.
Messen Sie den Pfad selbst, bevor Sie Code anfassen. redis-cli -h redis.example.internal --latency sendet wiederholte PINGs und gibt die durchschnittliche Anfrage- und Antwortzeit aus, also Ihre Kosten pro Austausch. Multiplizieren Sie das mit der Anzahl sequenzieller Befehle in einer Nutzeraktion, und Sie haben eine Untergrenze für den Endpunkt.
Zählen Sie die Roundtrips, die eine Nutzeraktion wirklich braucht
Nehmen Sie einen einzelnen Endpunkt und schreiben Sie jeden Redis-Aufruf der Reihe nach auf, auch die, die in einem ORM, einer Session-Middleware oder einem Feature-Flag-Lookup versteckt sind. Die meisten langsamen Endpunkte machen zwanzig oder dreißig Aufrufe, wo drei reichen würden, und die versteckten sind meist die Mehrheit.
Eine Abhängigkeitskette lässt sich nicht zusammenklappen: Braucht der zweite Befehl den Schlüssel, den der erste zurückgibt, ist die Wartezeit real, und die einzigen Optionen sind ein anderes Datenmodell oder eine serverseitige Operation, die den ganzen Schritt auf einmal ausdrückt. Unabhängige Lesezugriffe sind ein anderer Fall, weil Sie nichts zwingt, die erste Antwort abzuwarten, bevor Sie die zweite senden. Markieren Sie jeden Aufruf als abhängig oder unabhängig, bevor Sie irgendetwas ändern, und die Form der Lösung wird offensichtlich.
Die Zahl, auf die es ankommt, sind sequenzielle Wartezeiten, nicht die Gesamtzahl der Befehle. Dreißig Befehle in einem Batch kosten trotzdem nur eine Wartezeit.
Bündeln Sie nur die Befehle, die nicht voneinander abhängen
Eine Pipeline stellt unabhängige Befehle in eine Warteschlange und sammelt die Antworten der Reihe nach ein, was N Wartezeiten zu einer macht. Die meisten Clients bieten sie als Block, Callback oder Context Manager statt als eigenen Verbindungsmodus an, prüfen Sie also die API, die Ihre installierte Version mitbringt. Der Gewinn skaliert mit der Roundtrip-Zeit, und genau deshalb zählt er über ein VPN und ist auf localhost kaum spürbar.
import redis
r = redis.Redis(host="redis.example.internal", port=6379, socket_timeout=2)
pipe = r.pipeline(transaction=False)
for key in ("user:1001", "user:1002", "user:1003"):
pipe.get(key)
print(pipe.execute())
Übergeben Sie transaction=False, wenn Sie nur bündeln wollen. Standardmäßig packt der Client den Batch in MULTI und EXEC, was ihn atomar macht und ändert, was andere Clients mittendrin beobachten können. Atomarität ist eine Korrektheitsentscheidung, kein Leistungsregler, und eine Pipeline ist keine Transaktion, nur weil beide Roundtrips reduzieren.
Lesen Sie jede Antwort, nicht nur das Absenden des Batches. Ein eingereihter Befehl kann einen Fehler zurückgeben, während der Batch als Ganzes durchläuft, und ein Client, der die Antwortliste wegwirft, macht aus einem fehlgeschlagenen Schreibvorgang einen stillen. Vergleichen Sie die Länge der Antwortliste mit der Anzahl der eingereihten Befehle.
Halten Sie die Verbindung zwischen Aktionen am Leben
Jede neue Verbindung zahlt für Namensauflösung, einen TCP-Handshake, einen TLS-Handshake und ein AUTH, und über ein VPN ist jedes davon ein Roundtrip, den Sie nicht wegbündeln können. Erstellen Sie den Client einmal pro Prozess und verwenden Sie ihn wieder, oder nutzen Sie den Pool Ihrer Bibliothek mit begrenzter Größe. Wiederverbinden pro Anfrage ist nach den Wartezeiten pro Befehl die zweithäufigste Ursache dieses Symptoms.
Halten Sie den privaten Hostnamen in der Konfiguration statt einer wörtlichen Adresse. Zertifikatsprüfung und Dienstidentität gelten auch in einem privaten Netz, und das Festnageln einer IP bricht in dem Moment, in dem der Anbieter ein Subnetz umnummeriert. Ist die Auflösung der langsame Teil, reparieren Sie den Resolver-Pfad statt des Verbindungsstrings.
Setzen Sie ein explizites Socket-Timeout, damit ein hängender Tunnel schnell scheitert, statt einen Worker zu blockieren. Bestätigen Sie dann, dass der Pool wirklich wiederverwendet wird, indem Sie die Verbindungserstellung im Client protokollieren.
Dimensionieren Sie den Batch, bevor er zum Speicherproblem wird
Redis hält eingereihte Antworten für die Dauer eines Batches, und der Client hält danach das geparste Ergebnis. Eine Pipeline mit zehntausend großen Werten tauscht Netzwerkwartezeiten gegen Speicherspitzen und lange Pausen an beiden Enden.
Wählen Sie die Batch-Größe nach Antwortvolumen statt nach Befehlanzahl. Ein paar hundert kleine Befehle in einem Batch sind meist angenehm, hundert Payloads von mehreren Kilobyte nicht. Teilen Sie nach Bytes sowie nach Anzahl, und halten Sie jeden Batch im Latenzbudget, das der Endpunkt tatsächlich hat.
Messen Sie nach der Änderung Ende zu Ende: redis-cli -h redis.example.internal --latency-history zeigt, ob der Pfad selbst stabil ist, und Ihr eigener Anfrage-Timer zeigt, ob der Endpunkt besser wurde. Ist der Endpunkt weiterhin langsam, während der Latenzverlauf flach ist, sind die restlichen Kosten Serverarbeit oder Ihre eigene Serialisierung, und der nächste Schritt ist ein Profil des Handlers statt einer weiteren Batch-Änderung.