Wie in , es gab ein Problem mit dem verteilten Dienst, nennen wir diesen Dienst Elvin. Dieses Mal habe ich das Problem nicht selbst entdeckt, mir wurde von den Jungs aus dem Kundenbereich berichtet.
Eines Tages wachte ich von einem unzufriedenen Schreiben ĂŒber hohe Latenzen bei Elvin auf, den wir bald starten wollten. Insbesondere hatte der Kunde eine 99. Perzentil-Latenz von etwa 50 ms, viel höher als unser Latenzbudget. Es war erstaunlich, da ich den Dienst, insbesondere bezĂŒglich der Latenz, grĂŒndlich getestet hatte, schlieĂlich ist das ein hĂ€ufiges Beschwerdethema.
Bevor ich Elvin fĂŒr Tests freigab, fĂŒhrte ich viele Experimente mit 40.000 Anfragen pro Sekunde (QPS) durch, alle wiesen eine Latenz von weniger als 10 ms auf. Ich war bereit zu behaupten, dass ich mit ihren Ergebnissen nicht einverstanden bin. Aber als ich das Schreiben erneut betrachtete, fiel mir etwas Neues auf: Ich hatte die Bedingungen, die sie erwĂ€hnten, definitiv nicht getestet, ihre QPS war viel niedriger als meine. Ich testete bei 40k QPS, sie jedoch nur bei 1k. Ich fĂŒhrte ein weiteres Experiment durch, diesmal mit niedrigerem QPS, nur um sie zu besĂ€nftigen.
Da ich darĂŒber in einem Blog schreibe â Sie haben wahrscheinlich schon verstanden: Ihre Zahlen waren korrekt. Ich ĂŒberprĂŒfte meinen virtuellen Clienten immer wieder, mit dem gleichen Ergebnis: Eine niedrige Anzahl an Anfragen erhöht nicht nur die Latenz, sondern erhöht auch die Anzahl der Anfragen mit einer Latenz von mehr als 10 ms. Mit anderen Worten, wenn bei 40k QPS etwa 50 Anfragen pro Sekunde 50 ms ĂŒberschritten, waren es bei 1k QPS jede Sekunde 100 Anfragen ĂŒber 50 ms. Ein Paradoxon!

Die Suche eingrenzen
Wenn man mit einem Latenzproblem in einem verteilten System mit vielen Komponenten konfrontiert ist, sollte man zuerst eine kurze Liste von VerdÀchtigen erstellen. Lassen Sie uns tiefer in die Architektur von Elvin eintauchen:

Ein guter Ausgangspunkt ist die Liste der durchgefĂŒhrten Ein-/Ausgaben (Netzwerkaufrufe / Plattenzugriffe usw.). Versuchen wir herauszufinden, wo die Latenz liegt. Abgesehen von den offensichtlichen Ein-/Ausgaben mit dem Client macht Elvin einen zusĂ€tzlichen Schritt: Er fragt ein Datenspeicher ab. Da dieses Speicher jedoch im gleichen Cluster wie Elvin arbeitet, sollte die Latenz dort geringer sein als beim Client. Also die Liste der VerdĂ€chtigen:
- Netzwerkaufruf vom Client zu Elvin.
- Netzwerkanruf von Elvin an den Datenspeicher.
- Suche auf der Festplatte im Datenspeicher.
- Netzwerkanruf aus dem Datenspeicher an Elvin.
- Netzwerkanruf von Elvin an den Client.
Lass uns versuchen, einige Punkte auszuschlieĂen.
Der Datenspeicher ist nicht das Problem.
Zuerst habe ich Elvin in einen ping-ping-Server verwandelt, der keine Anfragen bearbeitet. Bei Erhalt einer Anfrage gibt er eine leere Antwort zurĂŒck. Wenn die Verzögerung abnimmt, liegt das Problem in Elvins oder des Datenspeichers Implementierung â nichts Ungewöhnliches. Im ersten Experiment erhalten wir folgendes Diagramm:

Wie wir sehen, gibt es bei Verwendung des ping-ping-Servers keine Verbesserungen. Das bedeutet, dass der Datenspeicher die Verzögerung nicht erhöht, und die Liste der VerdÀchtigen halbiert sich:
- Netzwerkaufruf vom Client zu Elvin.
- Netzwerkanruf von Elvin an den Client.
Super! Die Liste wird schnell kĂŒrzer. Ich dachte, ich hĂ€tte die Ursache fast herausgefunden.
, wĂŒrden Sie den Port
Jetzt ist es an der Zeit, Ihnen einen neuen Akteur vorzustellen: . Das ist eine Open-Source-Bibliothek von Google fĂŒr die Interprozesskommunikation . Obwohl , wĂŒrden Sie den Port gut optimiert und weit verbreitet ist, habe ich es zum ersten Mal in einem System dieser GröĂenordnung verwendet, und ich erwartete, dass meine Implementierung, um es milde auszudrĂŒcken, suboptimal sein wĂŒrde.
Vorhandensein , wĂŒrden Sie den Port Im Stack entstand eine neue Frage: Vielleicht ist es meine Implementierung oder die Bibliothek selbst, die das Verzögerungsproblem verursacht? FĂŒgen wir die neue VerdĂ€chtige zur Liste hinzu: , wĂŒrden Sie den Port Der Client ruft die Bibliothek auf.
- auf dem Client fĂŒhrt einen Netzwerkanruf der Bibliothek aus.
, wĂŒrden Sie den Port - Bibliothek
, wĂŒrden Sie den PortAuf dem Server, wĂŒrden Sie den Portwird Elvin angesprochen (keine Operation im Fall des ping-pong-Servers). - Bibliothek
, wĂŒrden Sie den PortDamit Sie verstehen, wie der Code aussieht, unterscheidet sich meine Implementierung von Client/Elvin nicht wesentlich von client-server
Beispielen fĂŒr async. .
die Verwendung eines eigenen (schablonisierten?) Thread-Modells ermöglicht, in dem sich der AusfĂŒhrungsstack und die Benutzimplementierung vermischen. Um es einfach zu halten, werden wir uns an dieses Modell halten.
, wĂŒrden Sie den PortProfilierung wird alles klĂ€ren., wĂŒrden Sie den PortNachdem ich den Datenspeicher ausgeschlossen habe, dachte ich, ich wĂ€re fast fertig: "Jetzt ist es einfach! Wir wenden das Profil an und finden heraus, wo die Verzögerung auftritt." Ich
bin ein groĂer Fan prĂ€ziser Profilierung,
denn die CPU ist sehr schnell und oft nicht der Engpass. Die meisten Verzögerungen treten auf, wenn der Prozessor die Verarbeitung anhalten muss, um etwas anderes zu tun. PrĂ€zise CPU-Profilierung wurde genau dafĂŒr entwickelt: Sie zeichnet alles genau auf. , da CPUs sehr schnell sind und meist nicht der Engpass. Die meisten Verzögerungen treten auf, wenn der Prozessor die Verarbeitung anhalten muss, um etwas anderes zu tun. PrĂ€zises CPU-Profiling ist genau dafĂŒr gedacht: Es zeichnet alles genau auf. und verdeutlicht, wo Verzögerungen auftreten.
Ich habe vier Profile erstellt: eines fĂŒr hohe QPS (geringe Verzögerung) und eines mit einem Ping-Pong-Server bei niedriger QPS (hohe Verzögerung), sowohl auf der Client- als auch auf der Serverseite. Und nur fĂŒr den Fall habe ich auch ein CPU-Profilmuster genommen. Beim Vergleich der Profile suche ich normalerweise nach einem anomalen Call-Stack. Zum Beispiel gibt es auf der schlechten Seite mit hoher Verzögerung viel mehr Kontextwechsel (zehnmal oder mehr). Aber in meinem Fall war die Anzahl der Kontextwechsel nahezu identisch. Zu meinem Entsetzen gab es dort nichts Wesentliches.
ZusÀtzliche Fehlersuche
Ich war verzweifelt. Ich wusste nicht, welche anderen Werkzeuge ich verwenden sollte, und mein nĂ€chster Plan bestand im Wesentlichen darin, Experimente mit verschiedenen Variationen durchzufĂŒhren, anstatt das Problem klar zu diagnostizieren.
Was wÀre, wenn
Von Anfang an machte ich mir Sorgen ĂŒber die spezifische Verzögerungszeit von 50 ms. Das ist eine sehr hohe Zeit. Ich beschloss, Teile aus dem Code herauszuschneiden, bis ich genau herausfinden konnte, welcher Teil diesen Fehler verursacht. Dann folgte ein Experiment, das funktionierte.
Wie immer scheint es im Nachhinein offensichtlich zu sein. Ich stellte den Client auf die gleiche Maschine wie Alvin und sendete eine Anfrage an localhost. Und die erhöhte Verzögerung verschwand!

Etwas stimmte mit dem Netzwerk nicht.
Netzwerkingenieur-FĂ€higkeiten erlernen
Ich muss zugeben: Mein Wissen ĂŒber Netzwerktechnologie ist schrecklich, besonders wenn man bedenkt, dass ich tĂ€glich damit arbeite. Aber das Netzwerk war der HauptverdĂ€chtige, und ich musste lernen, wie man es debuggt.
GlĂŒcklicherweise schĂ€tzt das Internet diejenigen, die lernen möchten. Die Kombination aus ping und tracert schien ein guter Anfang zur Fehlersuche von Transportnetzwerkproblemen zu sein.
ZunĂ€chst startete ich auf Alvins TCP-Port. Ich verwendete die Standardeinstellungen - nichts Besonderes. Von ĂŒber tausend Pings ĂŒberschritt keiner 10 ms, mit Ausnahme des ersten zum AufwĂ€rmen. Das steht im Widerspruch zu der beobachteten Verzögerung von 50 ms im 99. Perzentil: dort hĂ€tten wir fĂŒr jede 100 Anfragen etwa eine Anfrage mit einer Verzögerung von 50 ms sehen mĂŒssen.
Dann versuchte ich : vielleicht liegt das Problem an einem der Knoten auf dem Weg zwischen Alvin und dem Client. Aber auch der Tracer kam mit leeren HĂ€nden zurĂŒck.
So war die Verzögerung nicht mein Code, nicht die Implementierung von gRPC und nicht das Netzwerk. Ich begann mir Sorgen zu machen, dass ich das nie verstehen wĂŒrde.
Jetzt, welches Betriebssystem nutzen wir?
, wĂŒrden Sie den Port wird hĂ€ufig in Linux verwendet, aber fĂŒr Windows ist es exotisch. Ich entschied mich, ein Experiment durchzufĂŒhren, das erfolgreich war: Ich erstellte eine virtuelle Linux-Maschine, kompilierte Alvin fĂŒr Linux und setzte ihn ein.

Und das ist das Ergebnis: Auf dem ping-pong Server in Linux gab es nicht die gleichen Verzögerungen wie beim entsprechenden Windows-Knoten, obwohl die Datenquelle identisch war. Es stellt sich heraus, dass das Problem in der gRPC-Implementierung fĂŒr Windows lag.
Der Nagle-Algorithmus
Ich dachte die ganze Zeit, dass mir ein Flag fehlt , wĂŒrden Sie den Port. Jetzt habe ich verstanden, dass es in "Windows" fehlt. , wĂŒrden Sie den Port Ich fand eine interne RPC-Bibliothek, von der ich sicher war, dass sie gut fĂŒr alle gesetzten Flags funktioniert. . Dann fĂŒgte ich all diese Flags in gRPC hinzu und setzte Alvin auf Windows im korrigierten ping-pong Server unter Windows ein!

Fast fertig: Ich begann, die hinzugefĂŒgten Flags nacheinander zu löschen, bis die Regression zurĂŒckkam, sodass ich ihre Ursache genau bestimmen konnte. Es war das berĂŒchtigte , der Schalter des Nagle-Algorithmus.
versucht, die Anzahl der ĂŒber das Netzwerk gesendeten Pakete zu reduzieren, indem er die Ăbertragung von Nachrichten verzögert, bis die PaketgröĂe einen bestimmten Byte-Wert ĂŒberschreitet. WĂ€hrend dies fĂŒr den durchschnittlichen Benutzer angenehm sein kann, ist es fĂŒr Echtzeitserver destruktiv, da das Betriebssystem einige Nachrichten zurĂŒckhĂ€lt und Verzögerungen bei niedrigem QPS verursacht. Auf , wĂŒrden Sie den Port war dieses Flag in der Linux-Implementierung fĂŒr TCP-Sockets gesetzt, jedoch nicht fĂŒr Windows. Das .
Fazit
Hohe Verzögerung bei niedrigem QPS wurde durch die Optimierung des Betriebssystems verursacht. RĂŒckblickend zeigte das Profiling keine Verzögerung, da es im Kernel-Modus und nicht im durchgefĂŒhrt wurde. Ich weiĂ nicht, ob man den Nagle-Algorithmus durch ETW-Traces beobachten kann, aber das wĂ€re interessant.
Was das localhost-Experiment betrifft, hatte es wahrscheinlich nichts mit dem tatsĂ€chlichen Netzwerkcode zu tun, und der Nagle-Algorithmus wurde nicht aktiviert, sodass die Verzögerungsprobleme verschwanden, als der Client ĂŒber localhost auf Alvin zugriff.
Das nÀchste Mal, wenn Sie eine Erhöhung der Verzögerung bei sinkender Anzahl von Anfragen pro Sekunde sehen, sollte der Nagle-Algorithmus auf Ihrer VerdÀchtigenliste stehen!
Quelle: habr.com
