Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

Som i de fleste indlæg, der er et problem med en distribueret tjeneste, lad os kalde denne tjeneste Alvin. Denne gang opdagede jeg ikke problemet selv, fortalte fyrene fra klientsiden mig.

En dag vågnede jeg op til en utilfreds e-mail på grund af lange forsinkelser med Alvin, som vi planlagde at lancere snart. Specifikt oplevede klienten 99. percentil latency i området 50 ms, hvilket er et godt stykke over vores latensbudget. Dette var overraskende, da jeg testede tjenesten grundigt, især på latenstid, hvilket er en almindelig klage.

Før jeg satte Alvin i test, kørte jeg en masse eksperimenter med 40 forespørgsler i sekundet (QPS), som alle viste latens på mindre end 10ms. Jeg var klar til at erklære, at jeg ikke var enig i deres resultater. Men da jeg kiggede igen på brevet, bemærkede jeg noget nyt: Jeg havde ikke lige testet de forhold, de nævnte, deres QPS var meget lavere end min. Jeg testede ved 40k QPS, men de kun ved 1k. Jeg kørte endnu et eksperiment, denne gang med en lavere QPS, bare for at formilde dem.

Da jeg blogger om dette, har du sikkert allerede fundet ud af, at deres tal var rigtige. Jeg testede min virtuelle klient igen og igen, med det samme resultat: et lavt antal anmodninger øger ikke kun latensen, men øger antallet af anmodninger med en latenstid på mere end 10 ms. Med andre ord, hvis omkring 40 anmodninger pr. sekund ved 50k QPS oversteg 50 ms, så var der ved 1k QPS 100 anmodninger over 50 ms hvert sekund. Paradoks!

Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

Indsnævre søgningen

Når man står over for et latensproblem i et distribueret system med mange komponenter, er det første skridt at oprette en kort liste over mistænkte. Lad os grave lidt dybere ned i Alvins arkitektur:

Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

Et godt udgangspunkt er en liste over gennemførte I/O-overgange (netværksopkald/diskopslag osv.). Lad os prøve at finde ud af, hvor forsinkelsen er. Udover den åbenlyse I/O med klienten, tager Alvin et ekstra skridt: han får adgang til datalageret. Dette lager opererer dog i samme klynge som Alvin, så latensen der bør være mindre end hos klienten. Så listen over mistænkte:

  1. Netværksopkald fra klient til Alvin.
  2. Netværksopkald fra Alvin til datalageret.
  3. Søg på disk i datalageret.
  4. Netværksopkald fra datavarehuset til Alvin.
  5. Netværksopkald fra Alvin til en klient.

Lad os prøve at strege nogle punkter ud.

Datalagring har intet med det at gøre

Det første jeg gjorde var at konvertere Alvin til en ping-ping-server, der ikke behandler anmodninger. Når den modtager en anmodning, returnerer den et tomt svar. Hvis latensen falder, er en fejl i Alvin- eller data warehouse-implementeringen ikke noget uhørt. I det første eksperiment får vi følgende graf:

Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

Som du kan se, er der ingen forbedring, når du bruger ping-ping-serveren. Det betyder, at datavarehuset ikke øger latensen, og listen over mistænkte er halveret:

  1. Netværksopkald fra klient til Alvin.
  2. Netværksopkald fra Alvin til en klient.

Store! Listen krymper hurtigt. Jeg troede, jeg næsten havde fundet ud af årsagen.

gRPC

Nu er det tid til at præsentere dig for en ny spiller: gRPC. Dette er et open source-bibliotek fra Google til kommunikation undervejs RPC. selv gRPC godt optimeret og meget brugt, det var første gang jeg brugte det på et system af denne størrelse, og jeg forventede, at min implementering ville være suboptimal - for at sige det mildt.

tilgængelighed gRPC i stakken gav anledning til et nyt spørgsmål: måske er det min implementering eller mig selv gRPC forårsager latency problem? Tilføjelse af en ny mistænkt til listen:

  1. Kunden ringer til biblioteket gRPC
  2. bibliotek gRPC foretager et netværksopkald til biblioteket på klienten gRPC på serveren
  3. bibliotek gRPC kontakter Alvin (ingen handling i tilfælde af ping-pong server)

For at give dig en idé om, hvordan koden ser ud, er min klient/Alvin implementering ikke meget forskellig fra klient-serveren. asynkrone eksempler.

Bemærk: Ovenstående liste er en smule forenklet, fordi gRPC gør det muligt at bruge din egen (skabelon?) gevindmodel, hvor udførelsesstakken er flettet sammen gRPC og brugerimplementering. For nemheds skyld vil vi holde os til denne model.

Profilering vil løse alt

Efter at have streget datalagrene over, troede jeg, at jeg næsten var færdig: “Nu er det nemt! Lad os anvende profilen og finde ud af, hvor forsinkelsen opstår." jeg stor fan af præcisionsprofilering, fordi CPU'er er meget hurtige og oftest ikke er flaskehalsen. De fleste forsinkelser opstår, når processoren skal stoppe behandlingen for at gøre noget andet. Præcis CPU-profilering gør netop det: den registrerer alt nøjagtigt kontekstskifter og gør det klart, hvor forsinkelser opstår.

Jeg tog fire profiler: med høj QPS (lav latency) og med en ping-pong-server med lav QPS (høj latency), både på klientsiden og på serversiden. Og for en sikkerheds skyld tog jeg også en prøveprocessorprofil. Når jeg sammenligner profiler, leder jeg normalt efter en unormal opkaldsstack. For eksempel, på den dårlige side med høj latency er der mange flere kontekstskift (10 gange eller mere). Men i mit tilfælde var antallet af kontekstskift næsten det samme. Til min rædsel var der ikke noget væsentligt der.

Yderligere fejlretning

Jeg var desperat. Jeg vidste ikke, hvilke andre værktøjer jeg kunne bruge, og min næste plan var i det væsentlige at gentage eksperimenterne med forskellige variationer i stedet for klart at diagnosticere problemet.

Hvad hvis

Helt fra begyndelsen var jeg bekymret over den specifikke 50ms latency. Det er en meget stor tid. Jeg besluttede, at jeg ville skære bidder ud af koden, indtil jeg kunne finde ud af præcis, hvilken del der forårsagede denne fejl. Så kom et eksperiment, der virkede.

Som sædvanlig ser det i bakspejlet ud til, at alt var indlysende. Jeg placerede klienten på samme maskine som Alvin - og sendte en forespørgsel til localhost. Og stigningen i latens er væk!

Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

Der var noget galt med netværket.

At lære netværksingeniørfærdigheder

Jeg må indrømme: min viden om netværksteknologier er forfærdelig, især i betragtning af, at jeg arbejder med dem hver dag. Men netværket var den primære mistænkte, og jeg havde brug for at lære at debugge det.

Heldigvis elsker internettet dem, der gerne vil lære. Kombinationen af ​​ping og tracert virkede som en god nok start på fejlfinding af netværkstransportproblemer.

Først lancerede jeg PsPing til Alvins TCP-port. Jeg brugte standardindstillingerne - ikke noget særligt. Ud af mere end tusind ping oversteg ingen 10 ms, med undtagelse af den første til opvarmning. Dette er i modsætning til den observerede stigning i latens på 50 ms ved 99. percentilen: Der skulle vi for hver 100 anmodninger have set omkring én anmodning med en latenstid på 50 ms.

Så prøvede jeg tracert: Der kan være et problem ved en af ​​knudepunkterne langs ruten mellem Alvin og klienten. Men sporstoffet vendte også tomhændet tilbage.

Så det var ikke min kode, gRPC-implementeringen eller netværket, der forårsagede forsinkelsen. Jeg begyndte at bekymre mig om, at jeg aldrig ville forstå dette.

Hvilket OS har vi nu

gRPC meget brugt på Linux, men eksotisk på Windows. Jeg besluttede at prøve et eksperiment, som virkede: Jeg oprettede en virtuel Linux-maskine, kompilerede Alvin til Linux og implementerede den.

Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

Og her er hvad der skete: Linux ping-pong-serveren havde ikke de samme forsinkelser som en lignende Windows-vært, selvom datakilden ikke var anderledes. Det viser sig, at problemet er i gRPC-implementeringen til Windows.

Nagles algoritme

Hele denne tid troede jeg, at jeg manglede et flag gRPC. Nu forstår jeg, hvad det egentlig er gRPC Windows flag mangler. Jeg fandt et internt RPC-bibliotek, som jeg var overbevist om ville fungere godt for alle angivet flag Winsock. Derefter tilføjede jeg alle disse flag til gRPC og installerede Alvin på Windows, i en patchet Windows-ping-pong-server!

Nogle gange er mere mindre. Når belastningen reduceres, resulterer det i stigende latenstid

næsten Udført: Jeg begyndte at fjerne de tilføjede flag et ad gangen, indtil regressionen vendte tilbage, så jeg kunne udpege årsagen. Det var berygtet TCP_NODELAY, Nagles algoritme-switch.

Nagles algoritme forsøger at reducere antallet af pakker sendt over et netværk ved at forsinke transmissionen af ​​meddelelser, indtil pakkestørrelsen overstiger et vist antal bytes. Selvom dette kan være rart for den gennemsnitlige bruger, er det ødelæggende for realtidsservere, da operativsystemet vil forsinke nogle meddelelser, hvilket forårsager forsinkelser ved lav QPS. U gRPC dette flag blev sat i Linux-implementeringen til TCP-sockets, men ikke i Windows. Jeg er denne rettet.

Konklusion

Den højere latenstid ved lav QPS var forårsaget af OS-optimering. Set i bakspejlet opdagede profilering ikke latens, fordi det blev udført i kernetilstand snarere end i brugertilstand. Jeg ved ikke, om Nagles algoritme kan observeres gennem ETW-optagelser, men det ville være interessant.

Hvad angår localhost-eksperimentet, rørte det sandsynligvis ikke den faktiske netværkskode, og Nagles algoritme kørte ikke, så latensproblemerne forsvandt, da klienten nåede Alvin gennem localhost.

Næste gang du ser en stigning i latens, når antallet af anmodninger pr. sekund falder, burde Nagles algoritme være på din liste over mistænkte!

Kilde: www.habr.com

Tilføj en kommentar