Zoals in
Op een dag werd ik wakker met een ontevreden e-mail vanwege lange vertragingen bij Alvin, die we in de nabije toekomst wilden lanceren. Concreet ondervond de klant een latentie van het 99e percentiel in de buurt van 50 ms, ruim boven ons latentiebudget. Dit was verrassend omdat ik de service uitgebreid heb getest, vooral op latentie, wat een veelgehoorde klacht is.
Voordat ik Alvin ging testen, heb ik veel experimenten uitgevoerd met 40 queries per seconde (QPS), die allemaal een latentie van minder dan 10 ms vertoonden. Ik was bereid te verklaren dat ik het niet eens was met hun resultaten. Maar toen ik nog eens naar de brief keek, merkte ik iets nieuws op: ik had de voorwaarden die ze noemden niet precies getest, hun QPS was veel lager dan de mijne. Ik heb getest op 40k QPS, maar zij alleen op 1k. Ik heb nog een experiment uitgevoerd, dit keer met een lagere QPS, gewoon om ze te sussen.
Omdat ik hierover blog, ben je er waarschijnlijk al achter dat hun cijfers klopten. Ik heb mijn virtuele client keer op keer getest, met hetzelfde resultaat: een laag aantal verzoeken verhoogt niet alleen de latentie, maar verhoogt ook het aantal verzoeken met een latentie van meer dan 10 ms. Met andere woorden, als bij 40k QPS ongeveer 50 verzoeken per seconde de 50 ms overschreden, dan waren er bij 1k QPS 100 verzoeken boven de 50 ms per seconde. Paradox!
De zoekopdracht beperken
Wanneer u wordt geconfronteerd met een latentieprobleem in een gedistribueerd systeem met veel componenten, is de eerste stap het maken van een korte lijst met verdachten. Laten we wat dieper ingaan op de architectuur van Alvin:
Een goed startpunt is een lijst met voltooide I/O-overgangen (netwerkoproepen/opzoeken van schijven, enz.). Laten we proberen erachter te komen waar de vertraging zit. Naast de voor de hand liggende I/O met de klant, zet Alvin nog een extra stap: hij krijgt toegang tot de datastore. Deze opslag werkt echter in hetzelfde cluster als Alvin, dus de latentie daar zou minder moeten zijn dan bij de client. Dus de lijst met verdachten:
- Netwerkoproep van klant naar Alvin.
- Netwerkoproep van Alvin naar de dataopslag.
- Zoek op schijf in het gegevensarchief.
- Netwerkoproep van het datawarehouse naar Alvin.
- Netwerkoproep van Alvin naar een klant.
Laten we proberen enkele punten door te strepen.
Gegevensopslag heeft er niets mee te maken
Het eerste dat ik deed was Alvin omzetten naar een ping-ping-server die geen verzoeken verwerkt. Wanneer het een verzoek ontvangt, retourneert het een leeg antwoord. Als de latency afneemt, is een bug in de Alvin- of datawarehouse-implementatie niets ongehoord. In het eerste experiment krijgen we de volgende grafiek:
Zoals u kunt zien, is er geen verbetering bij het gebruik van de ping-ping-server. Dit betekent dat het datawarehouse de latentie niet verhoogt en dat de lijst met verdachten wordt gehalveerd:
- Netwerkoproep van klant naar Alvin.
- Netwerkoproep van Alvin naar een klant.
Geweldig! De lijst wordt snel kleiner. Ik dacht dat ik de reden bijna had ontdekt.
gRPC
Dit is het moment om je voor te stellen aan een nieuwe speler: gRPC
goed geoptimaliseerd en veel gebruikt, dit was de eerste keer dat ik het gebruikte op een systeem van deze omvang en ik verwachtte dat mijn implementatie op zijn zachtst gezegd suboptimaal zou zijn.
beschikbaarheid gRPC
in de stapel gaf aanleiding tot een nieuwe vraag: misschien is het mijn implementatie of mijzelf gRPC
waardoor latentieprobleem? Een nieuwe verdachte aan de lijst toevoegen:
- De klant belt de bibliotheek
gRPC
- bibliotheek
gRPC
maakt een netwerkoproep naar de bibliotheek op de clientgRPC
op server - bibliotheek
gRPC
neemt contact op met Alvin (geen werking bij pingpongserver)
Om je een idee te geven van hoe de code eruit ziet: mijn client/Alvin-implementatie verschilt niet veel van de client-server-implementaties
Opmerking: de bovenstaande lijst is een beetje vereenvoudigd omdat
gRPC
maakt het mogelijk om uw eigen (template?) threading-model te gebruiken, waarbij de uitvoeringsstack met elkaar verweven isgRPC
en gebruikersimplementatie. Omwille van de eenvoud houden we ons aan dit model.
Profilering zal alles oplossen
Nadat ik de datastores had doorgestreept, dacht ik dat ik bijna klaar was: “Nu is het gemakkelijk! Laten we het profiel toepassen en uitzoeken waar de vertraging optreedt.” I
Ik heb vier profielen genomen: met hoge QPS (low latency) en met een pingpongserver met lage QPS (high latency), zowel aan de clientzijde als aan de serverzijde. En voor het geval dat, heb ik ook een voorbeeldprocessorprofiel genomen. Bij het vergelijken van profielen zoek ik meestal naar een afwijkende call-stack. Aan de slechte kant met hoge latentie zijn er bijvoorbeeld veel meer contextwisselingen (10 keer of meer). Maar in mijn geval was het aantal contextwisselingen vrijwel hetzelfde. Tot mijn grote schrik was er niets belangrijks aan de hand.
Extra foutopsporing
Ik was wanhopig. Ik wist niet welke andere hulpmiddelen ik kon gebruiken, en mijn volgende plan was in wezen om de experimenten met verschillende variaties te herhalen in plaats van een duidelijke diagnose van het probleem te stellen.
Wat als
Vanaf het allereerste begin maakte ik me zorgen over de specifieke latentie van 50 ms. Dit is een hele grote tijd. Ik besloot dat ik stukjes uit de code zou knippen totdat ik precies kon achterhalen welk onderdeel deze fout veroorzaakte. Toen kwam er een experiment dat werkte.
Zoals gewoonlijk lijkt achteraf gezien alles vanzelfsprekend te zijn. Ik plaatste de client op dezelfde machine als Alvin - en stuurde een verzoek naar localhost
. En de toename in latentie is verdwenen!
Er was iets mis met het netwerk.
Netwerkingenieurvaardigheden leren
Ik moet toegeven: mijn kennis van netwerktechnologieën is verschrikkelijk, vooral gezien het feit dat ik er elke dag mee werk. Maar het netwerk was de hoofdverdachte en ik moest leren hoe ik daar fouten in kon maken.
Gelukkig houdt internet van degenen die willen leren. De combinatie van ping en tracert leek een goed begin voor het oplossen van netwerktransportproblemen.
Ten eerste lanceerde ik
Toen probeerde ik het
Het was dus niet mijn code, de gRPC-implementatie of het netwerk dat de vertraging veroorzaakte. Ik begon me zorgen te maken dat ik dit nooit zou begrijpen.
Welk besturingssysteem gebruiken we nu?
gRPC
veel gebruikt op Linux, maar exotisch op Windows. Ik besloot een experiment uit te proberen, dat werkte: ik creëerde een virtuele Linux-machine, compileerde Alvin voor Linux en implementeerde deze.
En dit is wat er gebeurde: de Linux-pingpongserver had niet dezelfde vertragingen als een vergelijkbare Windows-host, hoewel de gegevensbron niet anders was. Het blijkt dat het probleem zit in de gRPC-implementatie voor Windows.
Het algoritme van Nagle
Al die tijd dacht ik dat ik een vlag miste gRPC
. Nu begrijp ik wat het werkelijk is gRPC
Windows-vlag ontbreekt. Ik vond een interne RPC-bibliotheek waarvan ik zeker wist dat deze goed zou werken voor alle ingestelde vlaggen
bijna Klaar: ik begon de toegevoegde vlaggen één voor één te verwijderen totdat de regressie terugkeerde, zodat ik de oorzaak kon achterhalen. Het was berucht
gRPC
deze vlag is ingesteld in de Linux-implementatie voor TCP-sockets, maar niet in Windows. ik ben dit
Conclusie
De hogere latentie bij lage QPS werd veroorzaakt door OS-optimalisatie. Achteraf gezien heeft profilering geen latentie gedetecteerd omdat het in de kernelmodus werd uitgevoerd in plaats van in
Wat het localhost-experiment betreft, het raakte waarschijnlijk de daadwerkelijke netwerkcode niet en het algoritme van Nagle werkte niet, dus de latentieproblemen verdwenen toen de client Alvin bereikte via localhost.
De volgende keer dat u een toename van de latentie ziet naarmate het aantal verzoeken per seconde afneemt, zou het algoritme van Nagle op uw lijst met verdachten moeten staan!
Bron: www.habr.com