Som i , uppstod ett problem med den distribuerade tjÀnsten, lÄt oss kalla den Elvin. Den hÀr gÄngen upptÀckte jag inte problemet sjÀlv, jag fick veta det av killarna frÄn klientsidan.
En gÄng vaknade jag av ett missnöjt mejl pÄ grund av stora förseningar med Elvin, som vi planerade att lansera inom kort. Specifikt stötte kunden pÄ en försening pÄ 99:e percentilen pÄ omkring 50 ms, mycket över vÄr budget för försening. Det var förvÄnande, eftersom jag noggrant testade tjÀnsten, speciellt för förseningar, eftersom det Àr en kÀlla till frekventa klagomÄl.
Innan jag skickade Elvin för testning genomförde jag mÄnga experiment med 40 000 förfrÄgningar per sekund (QPS), alla visade en försening pÄ mindre Àn 10 ms. Jag var beredd att hÀvda att jag inte höll med deras resultat. Men nÀr jag tittade pÄ mejlet igen, lade jag mÀrke till nÄgot nytt: jag testade tydligen inte de förhÄllanden de nÀmnde, deras QPS var betydligt lÀgre Àn min. Jag testade vid 40k QPS, medan de bara var vid 1k. Jag körde ytterligare ett experiment, denna gÄng med lÀgre QPS, bara för att tillfredsstÀlla dem.
Eftersom jag skriver om detta pÄ bloggen - ni har troligen redan förstÄtt: deras siffror visade sig vara korrekta. Jag kontrollerade min virtuella klient gÄng pÄ gÄng, alltid med samma resultat: ett lÄgt antal förfrÄgningar ökade inte bara förseningen, utan ökade ocksÄ antalet förfrÄgningar med fördröjning över 10 ms. Med andra ord, om vid 40k QPS omkring 50 förfrÄgningar per sekund överskred 50 ms, sÄ var det vid 1k QPS 100 förfrÄgningar över 50 ms varje sekund. Ett paradox!

SnÀva in sökningen
NÀr man stÄr inför ett problem med försening i ett distribuerat system med mÄnga komponenter, Àr det första steget att göra en kort lista över misstÀnkta. LÄt oss grÀva lite djupare i Elvins arkitektur:

En bra utgÄngspunkt Àr listan över utförda I/O-operationer (nÀtverksanrop/diskÄtkomst osv.). LÄt oss försöka ta reda pÄ var förseningen uppstÄr. Förutom den uppenbara I/O med klienten gör Elvin ett extra steg: han kontaktar datalagringen. Men detta lagringssystem fungerar i samma kluster som Elvin, sÄ dÀr borde förseningen vara mindre Àn med klienten. SÄ, listan över misstÀnkta:
- NÀtverksanrop frÄn klienten till Elvin.
- NÀtverksanrop frÄn Elvin till datalagringen.
- DiskÄtkomst i datalagringen.
- NÀtanrop frÄn datalagringen till Elvin.
- NÀtanrop frÄn Elvin till klienten.
LÄt oss försöka stryka vissa punkter.
Datalagringen Àr inte inblandad
Först konverterade jag Elvin till en ping-ping-server som inte hanterar förfrĂ„gningar. Vid mottagning av en förfrĂ„gan returnerar den ett tomt svar. Om fördröjningen minskar, Ă€r felet antingen i Elvin eller datalagringen â ingenting sĂ€rskilt ovanligt. I det första experimentet fĂ„r vi en sĂ„dan graf:

Som vi ser visar inga förbÀttringar sig vid anvÀndning av ping-ping-servern. Detta innebÀr att datalagringen inte ökar fördröjningen, och listan över misstÀnkta minskar med hÀlften:
- NÀtverksanrop frÄn klienten till Elvin.
- NÀtanrop frÄn Elvin till klienten.
Fantastiskt! Listan minskar snabbt. Jag trodde att jag nÀstan hade listat ut orsaken.
gRPC
Nu Ă€r det dags att presentera en ny aktör: . Detta Ă€r ett bibliotek med öppen kĂ€llkod frĂ„n Google för interprocesskommunikation . Ăven om gRPC det Ă€r vĂ€loptimerat och anvĂ€nds i stor utstrĂ€ckning, anvĂ€nde jag det för första gĂ„ngen i ett system i denna storlek, och jag förvĂ€ntade mig att min implementering skulle vara suboptimal â för att uttrycka det milt.
NÀrvaron av gRPC i stacken gav upphov till en ny frÄga: kanske det Àr min implementering eller sjÀlva gRPC som orsakar fördröjningen? LÄt oss lÀgga till en ny misstÀnkt i listan:
- Klienten anropar biblioteket
gRPC - Bibliotek
gRPCutför ett nÀtanrop till biblioteketgRPCpÄ servern - Bibliotek
gRPCkontaktar Elvin (inversion finns inte i fallet med ping-pong-server)
För att du ska fÄ en uppfattning om hur koden ser ut, skiljer sig min klient/Elvin-implementation inte mycket frÄn traditionella klient-server-exempel .
Notering: den ovanstÄende listan Àr nÄgot förenklad, eftersom
gRPCden möjliggör anvÀndning av en egen (mall?) trÄdmodell, dÀr exekveringsstackengRPCoch anvÀndarimplementeringen flÀtas samman. För enkelhetens skull kommer vi att hÄlla oss till denna modell.
Profileringsverktyg kommer att lösa allt
Genom att stryka datalagringarna trodde jag att jag nÀstan var klar: "Nu Àr det enkelt! LÄt oss tillÀmpa en profil och se var fördröjningen uppstÄr." Jag , eftersom CPUerna Àr mycket snabba och oftast inte Àr flaskhalsar. De flesta fördröjningar uppstÄr nÀr processorn mÄste avbryta bearbetningen för att göra nÄgot annat. Exakt CPU-profilering Àr just för detta: den registrerar noggrant alla och ger insikt om var fördröjningarna uppstÄr.
Jag tog fyra profiler: en för hög QPS (lÄg latens) och en med ping-pong-server pÄ lÄg QPS (hög latens), bÄde pÄ klientsidan och serversidan. Och bara för sÀkerhets skull tog jag ocksÄ ett prov pÄ CPU-profilen. NÀr jag jÀmför profilerna letar jag vanligtvis efter anomalier i stackspÄren. Till exempel pÄ den dÄliga sidan med hög latens sker det mycket fler kontextbyten (10 gÄnger eller mer). Men i mitt fall var antalet kontextbyten nÀstan detsamma. Till min förskrÀckelse fanns det inget vÀsentligt att hitta.
Ytterligare felsökning
Jag var i djup förtvivlan. Jag visste inte vilka andra verktyg jag kunde anvÀnda, och min nÀsta plan var i praktiken att upprepa experimenten med olika variationer istÀllet för att tydligt diagnostisera problemet.
Vad hÀnder om
FrÄn allra första början oroade jag mig för den specifika latensen pÄ 50 ms. Det Àr en mycket hög tid. Jag bestÀmde mig för att jag skulle skÀra bort delar av koden tills jag kunde lista ut exakt vilken del som orsakar detta fel. Sedan följde ett experiment som lyckades.
Som vanligt verkar det i efterhand som att allt var uppenbart. Jag satte klienten pĂ„ samma maskin som Elvin â och skickade en begĂ€ran till localhost. Och latensökningen försvann!

NÄgot var fel med nÀtverket.
Vi lÀr oss nÀtverksingenjörens fÀrdigheter
Jag mÄste erkÀnna: mina kunskaper om nÀtverkstekniker Àr fruktansvÀrda, sÀrskilt med tanke pÄ att jag arbetar med dem dagligen. Men nÀtverket var den huvudmisstÀnkte, och jag behövde lÀra mig hur man felsöker det.
Som tur Àr Àlskar internet dem som vill lÀra sig. Kombinationen av ping och tracert verkade vara en bra början för att felsöka problem med nÀtverkstrafiken.
Först körde jag pĂ„ TCP-porten för Elvin. Jag anvĂ€nde standardinstĂ€llningarna â inget speciellt. Av mer Ă€n tusen pingar översteg ingen 10 ms, förutom den första för uppvĂ€rmning. Detta stĂ„r i kontrast till den observerade latensen pĂ„ 50 ms i 99:e percentilen: dĂ€r skulle vi pĂ„ varje 100 förfrĂ„gningar se ungefĂ€r en förfrĂ„gan med en latens pĂ„ 50 ms.
Sedan försökte jag : kanske var problemet pÄ en av noderna lÀngs vÀgen mellan Elvin och klienten. Men traceroten kom tillbaka med tomma hÀnder.
SÄ orsaken till latensen var varken min kod, implementationen av gRPC eller nÀtverket. Jag började redan oro mig för att jag aldrig skulle förstÄ detta.
Vilken operativsystem anvÀnder vi nu
gRPC Àr allmÀnt anvÀnt i Linux, men för Windows Àr det exotiskt. Jag beslutade mig för att göra ett experiment som fungerade: jag skapade en virtuell Linux-maskin, kompilerade Alvin för Linux och distribuerade den.

Och hÀr Àr vad som hÀnde: i Linux ping-pong-servern fanns det inte de förseningar som den motsvarande Windows-noden hade, trots att datakÀllan inte skilde sig. Det visar sig att problemet Àr implementeringen av gRPC för Windows.
Nagle-algoritmen
Under hela denna tid trodde jag att jag saknade en flagga gRPC. Nu förstÄr jag att det egentligen Àr gRPC Windows-flaggan som saknas. Jag hittade ett internt RPC-bibliotek dÀr jag var sÀker pÄ att det fungerade bra för alla installerade flaggor . Jag la sedan till alla dessa flaggor i gRPC och distribuerade Alvin pÄ Windows, i den korrigerade ping-pong-servern för Windows!

NÀstan klart: jag började ta bort de tillagda flaggorna en efter en tills regressionen Äterkom, sÄ jag kunde exakt identifiera orsaken. Det var den ökÀnda -switchen för Nagle-algoritmen.
försöker minska antalet paket som skickas över nĂ€tverket genom att fördröja överföringen av meddelanden tills paketstorleken överskrider ett visst antal byte. Ăven om det kan vara bra för den genomsnittliga anvĂ€ndaren, Ă€r det förödande för realtidsservrar eftersom operativsystemet kommer att fördröja vissa meddelanden, vilket orsakar fördröjningar vid lĂ„g QPS. PĂ„ gRPC var denna flagga instĂ€lld i Linux-implementeringen för TCP-sockets, men inte för Windows. Jag upptĂ€ckte detta .
Slutsats
Den stora fördröjningen vid lÄg QPS orsakades av optimeringen av operativsystemet. NÀr jag ser tillbaka, upptÀckte profileringen ingen fördröjning eftersom den genomfördes i kÀrlÀge, inte i Jag vet inte om man kan observera Nagle-algoritmen genom ETW-fÄngster, men det skulle vara intressant.
NÀr det gÀller localhost-experimentet, rörde det troligen inte den faktiska nÀtverkskoden, och Nagle-algoritmen kördes inte, sÄ fördröjningsproblemen försvann nÀr klienten kontaktade Alvin genom localhost.
NÀsta gÄng du ser en ökning av fördröjningarna vid en minskning av antalet begÀrningar per sekund, ska Nagle-algoritmen finnas med pÄ din lista över misstÀnkta!
KĂ€lla: habr.com
