Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

Kao u većina postova, postoji problem s distribuiranom uslugom, nazovimo ovu uslugu Alvin. Ovaj put nisam sam otkrio problem, obavijestili su me dečki sa strane klijenta.

Jednog dana probudio me nezadovoljan e-mail zbog dugih kašnjenja s Alvinom, koji smo planirali pokrenuti u bliskoj budućnosti. Konkretno, klijent je doživio 99. percentil kašnjenja u području od 50 ms, znatno iznad našeg proračuna kašnjenja. To je bilo iznenađujuće jer sam opsežno testirao uslugu, posebno na latenciju, što je uobičajena pritužba.

Prije nego što sam Alvina stavio na testiranje, proveo sam mnogo eksperimenata s 40 tisuća upita u sekundi (QPS), a svi su pokazali kašnjenje manje od 10 ms. Bio sam spreman izjaviti da se ne slažem s njihovim rezultatima. No, bacivši još jedan pogled na pismo, primijetio sam nešto novo: nisam točno testirao uvjete koje su spomenuli, njihov QPS bio je puno niži od mog. Testirao sam na 40k QPS, ali oni samo na 1k. Izveo sam još jedan eksperiment, ovaj put s nižim QPS-om, samo da ih umirim.

Budući da pišem blog o ovome, vjerojatno ste već shvatili da su njihove brojke bile točne. Testirao sam svoj virtualni klijent uvijek iznova, s istim rezultatom: mali broj zahtjeva ne samo da povećava latenciju, već povećava i broj zahtjeva s latencijom većom od 10 ms. Drugim riječima, ako je pri 40k QPS oko 50 zahtjeva u sekundi premašilo 50 ms, tada je pri 1k QPS bilo 100 zahtjeva iznad 50 ms svake sekunde. Paradoks!

Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

Sužavanje pretrage

Kada se suočite s problemom kašnjenja u distribuiranom sustavu s mnogo komponenti, prvi korak je stvoriti kratki popis osumnjičenih. Zaronimo malo dublje u Alvinovu arhitekturu:

Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

Dobra polazna točka je popis dovršenih I/O prijelaza (mrežni pozivi/traženje diska, itd.). Pokušajmo otkriti gdje je kašnjenje. Osim očitog I/O s klijentom, Alvin poduzima dodatni korak: pristupa pohrani podataka. Međutim, ova pohrana radi u istom klasteru kao Alvin, tako da bi latencija tamo trebala biti manja nego kod klijenta. Dakle, popis osumnjičenih:

  1. Mrežni poziv od klijenta do Alvina.
  2. Mrežni poziv od Alvina prema pohrani podataka.
  3. Pretraživanje na disku u pohrani podataka.
  4. Mrežni poziv iz skladišta podataka Alvinu.
  5. Mrežni poziv od Alvina klijentu.

Pokušajmo prekrižiti neke točke.

Pohrana podataka nema nikakve veze s tim

Prvo što sam učinio je pretvoriti Alvina u ping-ping poslužitelj koji ne obrađuje zahtjeve. Kada primi zahtjev, vraća prazan odgovor. Ako se latencija smanji, tada greška u implementaciji Alvina ili skladišta podataka nije ništa nečuveno. U prvom eksperimentu dobivamo sljedeći graf:

Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

Kao što vidite, nema poboljšanja pri korištenju ping-ping poslužitelja. To znači da skladište podataka ne povećava latenciju, a popis sumnjivih je prepolovljen:

  1. Mrežni poziv od klijenta do Alvina.
  2. Mrežni poziv od Alvina klijentu.

Sjajno! Popis se brzo smanjuje. Mislio sam da sam skoro shvatio razlog.

gRPC

Sada je vrijeme da vam predstavimo novog igrača: gRPC. Ovo je Googleova biblioteka otvorenog koda za komunikaciju unutar procesa RPC... Iako gRPC dobro optimiziran i široko korišten, ovo je bio moj prvi put da ga koristim na sustavu ove veličine i očekivao sam da će moja implementacija biti ispod optimalne - u najmanju ruku.

Dostupnost gRPC u hrpi potaknuo je novo pitanje: možda je to moja implementacija ili ja gRPC uzrokuje problem kašnjenja? Dodavanje novog osumnjičenika na popis:

  1. Klijent zove knjižnicu gRPC
  2. knjižnica gRPC upućuje mrežni poziv knjižnici na klijentu gRPC na poslužitelju
  3. knjižnica gRPC kontakti Alvin (nema rada u slučaju ping-pong poslužitelja)

Da vam dam predodžbu o tome kako kod izgleda, moja implementacija klijent/Alvin nije puno drugačija od one klijent-poslužitelj asinkroni primjeri.

Napomena: gornji popis je malo pojednostavljen jer gRPC omogućuje korištenje vlastitog (predloška?) modela niti, u kojem je stog izvršenja isprepleten gRPC i korisnička implementacija. Radi jednostavnosti, zadržat ćemo se na ovom modelu.

Profiliranje će sve popraviti

Nakon što sam prekrižio pohranu podataka, mislio sam da sam skoro gotov: “Sada je lako! Primijenimo profil i saznajmo gdje dolazi do kašnjenja.” ja veliki ljubitelj preciznog profiliranja, jer su procesori vrlo brzi i najčešće nisu usko grlo. Većina kašnjenja događa se kada procesor mora zaustaviti obradu da bi učinio nešto drugo. Accurate CPU Profiling čini upravo to: točno bilježi sve preklopnici konteksta i jasno pokazuje gdje dolazi do kašnjenja.

Uzeo sam četiri profila: s visokim QPS-om (niska latencija) i s ping-pong serverom s niskim QPS-om (visoka latencija), i na strani klijenta i na strani servera. I za svaki slučaj, uzeo sam i ogledni profil procesora. Kad uspoređujem profile, obično tražim nenormalan skup poziva. Na primjer, loša strana s visokom latencijom je mnogo više promjena konteksta (10 puta ili više). Ali u mom slučaju, broj promjena konteksta bio je gotovo isti. Na moj užas, tu nije bilo ničeg značajnog.

Dodatno otklanjanje pogrešaka

Bila sam očajna. Nisam znao koje bih druge alate mogao upotrijebiti, a moj sljedeći plan bio je u biti ponoviti eksperimente s različitim varijacijama, a ne jasno dijagnosticirati problem.

Što ako

Od samog početka sam bio zabrinut zbog kašnjenja od 50 ms. Ovo je jako veliko vrijeme. Odlučio sam da ću izrezati dijelove koda dok ne otkrijem točno koji dio uzrokuje ovu pogrešku. Zatim je uslijedio eksperiment koji je upalio.

Kao i obično, gledajući unatrag, čini se da je sve bilo očito. Postavio sam klijenta na isti stroj kao i Alvin - i poslao zahtjev na localhost. I povećanje latencije je nestalo!

Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

Nešto nije bilo u redu s mrežom.

Učenje vještina mrežnog inženjera

Moram priznati: moje znanje o mrežnim tehnologijama je grozno, pogotovo s obzirom na to da s njima radim svaki dan. Ali mreža je bila glavni osumnjičeni i morao sam naučiti kako otkloniti pogreške.

Srećom, Internet voli one koji žele učiti. Kombinacija pinga i tracerta činila se kao dovoljno dobar početak za otklanjanje grešaka u problemima mrežnog prijenosa.

Prvo sam pokrenuo PsPing na Alvinov TCP port. Koristio sam zadane postavke - ništa posebno. Od više od tisuću pingova niti jedan nije prelazio 10 ms, osim prvog za zagrijavanje. To je u suprotnosti s uočenim povećanjem latencije od 50 ms na 99. percentilu: tamo smo za svakih 100 zahtjeva trebali vidjeti otprilike jedan zahtjev s latencijom od 50 ms.

Onda sam pokušao tracert: Možda postoji problem na jednom od čvorova duž rute između Alvina i klijenta. Ali i tragač se vratio praznih ruku.

Dakle, kašnjenje nije uzrokovao moj kod, gRPC implementacija ili mreža. Počeo sam se brinuti da to nikad neću razumjeti.

Sada na kojem smo OS-u

gRPC široko korišten na Linuxu, ali egzotičan na Windowsima. Odlučio sam isprobati eksperiment koji je uspio: stvorio sam Linux virtualni stroj, kompajlirao Alvin za Linux i implementirao ga.

Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

I evo što se dogodilo: Linux poslužitelj za ping-pong nije imao ista kašnjenja kao sličan Windows host, iako izvor podataka nije bio drugačiji. Ispostavilo se da je problem u gRPC implementaciji za Windows.

Nagleov algoritam

Sve ovo vrijeme mislio sam da mi nedostaje zastava gRPC. Sada razumijem što je zapravo gRPC Windows zastavica nedostaje. Pronašao sam internu RPC biblioteku za koju sam bio uvjeren da će dobro funkcionirati za sve postavljene oznake Winsock. Zatim sam dodao sve te zastavice u gRPC i postavio Alvina na Windows, u zakrpani Windows ping-pong poslužitelj!

Ponekad je više manje. Kada smanjenje opterećenja rezultira povećanjem kašnjenja

skoro Gotovo: počeo sam uklanjati dodane oznake jednu po jednu dok se regresija nije vratila kako bih mogao točno odrediti uzrok. Bilo je zloglasno TCP_NODELAY, Nagleov algoritamski prekidač.

Nagleov algoritam pokušava smanjiti broj paketa koji se šalju preko mreže odgodom prijenosa poruka dok veličina paketa ne prijeđe određeni broj bajtova. Iako ovo može biti lijepo za prosječnog korisnika, destruktivno je za poslužitelje u stvarnom vremenu jer će OS odgoditi neke poruke, uzrokujući kašnjenja na niskom QPS-u. U gRPC ova je zastavica postavljena u implementaciji Linuxa za TCP utičnice, ali ne i u Windowsima. Ja sam ovo ispravljeno.

Zaključak

Veća latencija pri niskom QPS-u uzrokovana je optimizacijom OS-a. Retrospektivno, profiliranje nije otkrilo latenciju jer je učinjeno u načinu kernela, a ne u korisnički način rada. Ne znam može li se Nagleov algoritam promatrati kroz ETW snimke, ali bilo bi zanimljivo.

Što se tiče eksperimenta s lokalnim hostom, vjerojatno nije dotaknuo stvarni mrežni kod i Nagleov algoritam se nije pokrenuo, pa su problemi s kašnjenjem nestali kada je klijent došao do Alvina preko lokalnog hosta.

Sljedeći put kad vidite povećanje latencije kako se smanjuje broj zahtjeva u sekundi, Nagleov algoritam bi trebao biti na vašem popisu sumnjivih!

Izvor: www.habr.com

Dodajte komentar