Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

Som i de fleste innlegg, det er et problem med en distribuert tjeneste, la oss kalle denne tjenesten Alvin. Denne gangen oppdaget jeg ikke problemet selv, fortalte gutta fra klientsiden meg.

En dag våknet jeg til en misfornøyd e-post på grunn av lange forsinkelser med Alvin, som vi planla å lansere i nær fremtid. Nærmere bestemt opplevde klienten 99. persentil latens i området 50 ms, godt over latensbudsjettet vårt. Dette var overraskende da jeg testet tjenesten mye, spesielt på latens, som er en vanlig klage.

Før jeg satte Alvin i testing, kjørte jeg mange eksperimenter med 40 10 spørringer per sekund (QPS), alle viste ventetid på mindre enn 40 ms. Jeg var klar til å erklære at jeg ikke var enig i resultatene deres. Men ved å se på brevet igjen, la jeg merke til noe nytt: Jeg hadde ikke akkurat testet forholdene de nevnte, deres QPS var mye lavere enn min. Jeg testet på 1k QPS, men de bare på XNUMXk. Jeg kjørte et nytt eksperiment, denne gangen med lavere QPS, bare for å blidgjøre dem.

Siden jeg blogger om dette, har du sikkert allerede skjønt at tallene deres stemte. Jeg testet den virtuelle klienten min om og om igjen, med samme resultat: et lavt antall forespørsler øker ikke bare ventetiden, men øker antallet forespørsler med en ventetid på mer enn 10 ms. Med andre ord, hvis ca. 40 forespørsler per sekund ved 50k QPS oversteg 50 ms, så var det ved 1k QPS 100 forespørsler over 50 ms hvert sekund. Paradoks!

Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

Begrenser søket

Når du står overfor et latensproblem i et distribuert system med mange komponenter, er det første trinnet å lage en kort liste over mistenkte. La oss grave litt dypere inn i Alvins arkitektur:

Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

Et godt utgangspunkt er en liste over fullførte I/O-overganger (nettverksanrop/diskoppslag osv.). La oss prøve å finne ut hvor forsinkelsen er. Foruten den åpenbare I/O med klienten, tar Alvin et ekstra skritt: han får tilgang til datalageret. Denne lagringen opererer imidlertid i samme klynge som Alvin, så latensen der bør være mindre enn hos klienten. Så listen over mistenkte:

  1. Nettverksanrop fra klient til Alvin.
  2. Nettverksanrop fra Alvin til datalageret.
  3. Søk på disk i datalageret.
  4. Nettverksanrop fra datavarehuset til Alvin.
  5. Nettverksanrop fra Alvin til en klient.

La oss prøve å stryke ut noen punkter.

Datalagring har ingenting med det å gjøre

Det første jeg gjorde var å konvertere Alvin til en ping-ping-server som ikke behandler forespørsler. Når den mottar en forespørsel, returnerer den et tomt svar. Hvis ventetiden reduseres, er en feil i implementeringen av Alvin eller datavarehus ikke noe uhørt. I det første eksperimentet får vi følgende graf:

Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

Som du kan se, er det ingen forbedring når du bruker ping-ping-serveren. Dette betyr at datavarehuset ikke øker ventetiden, og listen over mistenkte er halvert:

  1. Nettverksanrop fra klient til Alvin.
  2. Nettverksanrop fra Alvin til en klient.

Flott! Listen krymper raskt. Jeg trodde jeg nesten hadde skjønt årsaken.

gRPC

Nå er det på tide å introdusere deg for en ny spiller: gRPC. Dette er et åpen kildekode-bibliotek fra Google for kommunikasjon underveis RPC. selv gRPC godt optimert og mye brukt, dette var første gang jeg brukte det på et system av denne størrelsen, og jeg forventet at implementeringen min skulle være suboptimal - for å si det mildt.

tilgjengelighet gRPC i stabelen ga opphav til et nytt spørsmål: kanskje det er min implementering eller meg selv gRPC forårsaker latensproblem? Legge til en ny mistenkt på listen:

  1. Kunden ringer biblioteket gRPC
  2. Bibliotek gRPC foretar et nettverksanrop til biblioteket på klienten gRPC på server
  3. Bibliotek gRPC kontakter Alvin (ingen operasjon ved ping-pong-server)

For å gi deg en ide om hvordan koden ser ut, er ikke min klient/Alvin-implementering mye forskjellig fra klient-serveren. asynkrone eksempler.

Merk: Listen ovenfor er litt forenklet fordi gRPC gjør det mulig å bruke din egen (mal?) trådmodell, der utførelsesstabelen er flettet sammen gRPC og brukerimplementering. For enkelhets skyld vil vi holde oss til denne modellen.

Profilering vil fikse alt

Etter å ha krysset over datalagrene trodde jeg at jeg nesten var ferdig: «Nå er det enkelt! La oss bruke profilen og finne ut hvor forsinkelsen oppstår." Jeg stor fan av presisjonsprofilering, fordi CPU-er er veldig raske og som oftest ikke er flaskehalsen. De fleste forsinkelser oppstår når prosessoren må stoppe behandlingen for å gjøre noe annet. Nøyaktig CPU-profilering gjør nettopp det: den registrerer alt nøyaktig kontekstbrytere og gjør det klart hvor forsinkelser oppstår.

Jeg tok fire profiler: med høy QPS (lav latency) og med en ping-pong-server med lav QPS (høy latency), både på klientsiden og på serversiden. Og for sikkerhets skyld tok jeg også en prøveprosessorprofil. Når jeg sammenligner profiler, ser jeg vanligvis etter en unormal anropsstabel. For eksempel, på den dårlige siden med høy latens er det mange flere kontekstbrytere (10 ganger eller mer). Men i mitt tilfelle var antallet kontekstbrytere nesten det samme. Til min skrekk var det ikke noe vesentlig der.

Ytterligere feilsøking

Jeg var desperat. Jeg visste ikke hvilke andre verktøy jeg kunne bruke, og min neste plan var i hovedsak å gjenta eksperimentene med forskjellige varianter i stedet for å tydelig diagnostisere problemet.

Hva om

Helt fra begynnelsen var jeg bekymret for den spesifikke 50ms latensen. Dette er en veldig stor tid. Jeg bestemte meg for at jeg ville kutte biter ut av koden til jeg kunne finne ut nøyaktig hvilken del som forårsaket denne feilen. Så kom et eksperiment som fungerte.

Som vanlig ser det i ettertid ut til at alt var åpenbart. Jeg plasserte klienten på samme maskin som Alvin - og sendte en forespørsel til localhost. Og økningen i latens er borte!

Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

Noe var galt med nettverket.

Lære ferdigheter i nettverksingeniører

Jeg må innrømme: kunnskapen min om nettverksteknologi er forferdelig, spesielt med tanke på at jeg jobber med dem hver dag. Men nettverket var hovedmistenkt, og jeg trengte å lære å feilsøke det.

Heldigvis elsker Internett de som vil lære. Kombinasjonen av ping og tracert virket som en god nok start på feilsøking av nettverkstransportproblemer.

Først lanserte jeg PsPing til Alvins TCP-port. Jeg brukte standardinnstillingene - ikke noe spesielt. Av mer enn tusen ping oversteg ingen 10 ms, med unntak av den første for oppvarming. Dette er i strid med den observerte økningen i latens på 50 ms ved 99. persentilen: der, for hver 100 forespørsler, burde vi ha sett omtrent én forespørsel med en ventetid på 50 ms.

Så prøvde jeg tracert: Det kan være et problem ved en av nodene langs ruten mellom Alvin og klienten. Men sporstoffet kom også tomhendt tilbake.

Så det var ikke koden min, gRPC-implementeringen eller nettverket som forårsaket forsinkelsen. Jeg begynte å bekymre meg for at jeg aldri ville forstå dette.

Hvilket OS har vi nå

gRPC mye brukt på Linux, men eksotisk på Windows. Jeg bestemte meg for å prøve et eksperiment, som fungerte: Jeg opprettet en virtuell Linux-maskin, kompilerte Alvin for Linux og implementerte den.

Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

Og her er hva som skjedde: Linux ping-pong-serveren hadde ikke de samme forsinkelsene som en lignende Windows-vert, selv om datakilden ikke var annerledes. Det viser seg at problemet ligger i gRPC-implementeringen for Windows.

Nagles algoritme

Hele denne tiden trodde jeg at jeg manglet et flagg gRPC. Nå forstår jeg hva det egentlig er gRPC Windows-flagg mangler. Jeg fant et internt RPC-bibliotek som jeg var sikker på ville fungere bra for alle flaggsett Winsock. Så la jeg alle disse flaggene til gRPC og distribuerte Alvin på Windows, i en lappet Windows ping-pong-server!

Noen ganger er mer mindre. Når belastningen reduseres, resulterer det i økende latens

nesten Ferdig: Jeg begynte å fjerne de lagte flaggene ett om gangen til regresjonen kom tilbake slik at jeg kunne finne årsaken. Det var beryktet TCP_NODELAY, Nagles algoritmebryter.

Nagles algoritme forsøker å redusere antall pakker som sendes over et nettverk ved å utsette overføringen av meldinger til pakkestørrelsen overstiger et visst antall byte. Selv om dette kan være fint for den gjennomsnittlige brukeren, er det ødeleggende for sanntidsservere, da operativsystemet vil forsinke noen meldinger, og forårsake forsinkelser ved lav QPS. U gRPC dette flagget ble satt i Linux-implementeringen for TCP-sockets, men ikke i Windows. Jeg er denne rettet opp.

Konklusjon

Den høyere latensen ved lav QPS ble forårsaket av OS-optimalisering. I ettertid oppdaget ikke profilering ventetid fordi det ble gjort i kjernemodus i stedet for i brukermodus. Jeg vet ikke om Nagles algoritme kan observeres gjennom ETW-fangster, men det ville vært interessant.

Når det gjelder localhost-eksperimentet, rørte det sannsynligvis ikke selve nettverkskoden og Nagles algoritme kjørte ikke, så latensproblemene forsvant da klienten nådde Alvin gjennom localhost.

Neste gang du ser en økning i ventetiden ettersom antall forespørsler per sekund reduseres, bør Nagles algoritme være på listen over mistenkte!

Kilde: www.habr.com

Legg til en kommentar