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

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

Jednog dana me probudio nezadovoljni e-mail zbog dugih kašnjenja s Alvinom, koji smo planirali uskoro pokrenuti. Konkretno, klijent je iskusio kašnjenje od 99. percentila u području od 50 ms, što je znatno iznad našeg budžeta za kašnjenje. Ovo je bilo iznenađujuće jer sam opsežno testirao uslugu, posebno na kašnjenju, što je uobičajena žalba.

Prije nego što sam stavio Alvina u testiranje, izveo sam mnogo eksperimenata sa 40 upita u sekundi (QPS), a svi su pokazali kašnjenje manju od 10 ms. Bio sam spreman da izjavim da se ne slažem sa njihovim rezultatima. Ali pogledavši još jednom pismo, primetio sam nešto novo: nisam baš testirao uslove koje su oni spomenuli, njihov QPS je bio mnogo niži od mog. Testirao sam na 40k QPS, ali oni samo na 1k. Izveo sam još jedan eksperiment, ovaj put sa nižim QPS-om, samo da ih umirim.

Pošto pišem na blogu o ovome, verovatno ste već shvatili da su njihovi brojevi tačni. Testirao sam svog virtuelnog klijenta iznova i iznova, sa istim rezultatom: mali broj zahteva ne samo da povećava kašnjenje, već povećava broj zahteva sa kašnjenjem većim od 10 ms. Drugim riječima, ako je pri 40k QPS oko 50 zahtjeva u sekundi premašilo 50 ms, onda 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 latencije

Sužavanje pretrage

Kada se suočite sa problemom kašnjenja u distribuiranom sistemu sa mnogo komponenti, prvi korak je kreiranje kratke liste osumnjičenih. Kopajmo malo dublje u Alvinovu arhitekturu:

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

Dobra polazna tačka je lista završenih I/O tranzicija (mrežni pozivi/pretraživanja diska, itd.). Pokušajmo otkriti gdje je kašnjenje. Pored očiglednog I/O sa klijentom, Alvin preduzima dodatni korak: pristupa skladištu podataka. Međutim, ovo skladište radi u istom klasteru kao i Alvin, tako da bi latencija tamo trebala biti manja nego kod klijenta. Dakle, lista osumnjičenih:

  1. Mrežni poziv od klijenta do Alvina.
  2. Mrežni poziv od Alvina prema spremištu podataka.
  3. Pretražite na disku u skladištu podataka.
  4. Mrežni poziv iz skladišta podataka za Alvina.
  5. Mrežni poziv od Alvina klijentu.

Pokušajmo precrtati neke tačke.

Skladištenje podataka nema nikakve veze s tim

Prva stvar koju sam uradio je konvertovanje Alvina u ping-ping server koji ne obrađuje zahteve. Kada primi zahtjev, vraća prazan odgovor. Ako se latencija smanji, onda greška u implementaciji Alvina ili skladišta podataka nije ništa nečuveno. U prvom eksperimentu dobijamo sledeći grafikon:

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

Kao što vidite, nema poboljšanja kada koristite ping-ping server. To znači da skladište podataka ne povećava latencije, a lista osumnjičenih je prepolovljena:

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

Odlično! Lista se brzo smanjuje. Mislio sam da sam skoro shvatio razlog.

gRPC

Sada je vrijeme da vas upoznamo sa novim igračem: gRPC. Ovo je biblioteka otvorenog koda od Googlea za komunikaciju u procesu RPC... Iako gRPC dobro optimiziran i široko korišten, ovo je bio moj prvi put da ga koristim na sistemu ove veličine i očekivao sam da će moja implementacija biti suboptimalna - u najmanju ruku.

dostupnost gRPC u stogu doveo do novog pitanja: možda je to moja implementacija ili ja gRPC uzrokuje problem kašnjenja? Dodavanje novog osumnjičenog na listu:

  1. Klijent poziva biblioteku gRPC
  2. biblioteka gRPC upućuje mrežni poziv biblioteci na klijentu gRPC na serveru
  3. biblioteka gRPC kontakti Alvin (nema operacije u slučaju ping-pong servera)

Da vam dam ideju kako izgleda kod, moja implementacija klijent/Alvin se ne razlikuje mnogo od klijent-server implementacije asinhronizirani primjeri.

Napomena: Gornja lista je malo pojednostavljena jer gRPC omogućava korištenje vašeg vlastitog (template?) threading modela, u kojem je izvršni stek isprepleten gRPC i implementacija korisnika. Radi jednostavnosti, mi ćemo se zadržati na ovom modelu.

Profilisanje će sve popraviti

Pošto sam precrtao skladišta podataka, mislio sam da sam skoro gotov: „Sada je lako! Primijenimo profil i saznamo gdje dolazi do kašnjenja.” I veliki ljubitelj preciznog profilisanja, jer su CPU-i veoma brzi i najčešće nisu usko grlo. Većina kašnjenja nastaje kada procesor mora prekinuti obradu da bi učinio nešto drugo. Precizno CPU profilisanje čini upravo to: tačno beleži sve prebacivanja konteksta i jasno stavlja do znanja gdje dolazi do kašnjenja.

Uzeo sam četiri profila: sa visokim QPS-om (malo kašnjenje) i sa ping-pong serverom sa niskim QPS-om (visoka latencija), i na strani klijenta i na strani servera. I za svaki slučaj, uzeo sam i uzorak profila procesora. Kada upoređujem profile, obično tražim anomalan stek poziva. Na primjer, na lošoj strani s velikom latencijom postoji mnogo više promjena konteksta (10 puta ili više). Ali u mom slučaju, broj prebacivanja konteksta bio je skoro isti. Na moj užas, tu nije bilo ništa značajno.

Dodatno otklanjanje grešaka

Bio sam očajan. Nisam znao koje bih druge alate mogao koristiti, a moj sljedeći plan je u suštini bio da ponovim eksperimente s različitim varijacijama umjesto da jasno dijagnosticiram problem.

Šta ako

Od samog početka sam bio zabrinut zbog specifičnog kašnjenja od 50 ms. Ovo je veoma veliki trenutak. Odlučio sam da ću izrezati komade koda dok ne shvatim koji je dio tačno uzrokovao ovu grešku. Zatim je uslijedio eksperiment koji je uspio.

Kao i obično, gledajući unazad, čini se da je sve bilo očigledno. Postavio sam klijenta na istu mašinu kao i Alvin - i poslao zahtev localhost. I povećanje latencije je nestalo!

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

Nešto nije u redu s mrežom.

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

Moram priznati: moje poznavanje mrežnih tehnologija je užasno, pogotovo ako se uzme u obzir da svakodnevno radim s njima. Ali mreža je bila glavni osumnjičeni i morao sam naučiti kako da je otklonim.

Srećom, internet voli one koji žele da uče. Kombinacija pinga i tracerta se činila kao dovoljno dobar početak za otklanjanje grešaka u mrežnom transportu.

Prvo sam lansirao PsPing na Alvinov TCP port. Koristio sam zadane postavke - ništa posebno. Od više od hiljadu pingova, nijedan nije prešao 10 ms, izuzev prvog za zagrevanje. Ovo je u suprotnosti sa uočenim povećanjem latencije od 50 ms na 99. percentilu: tamo, na svakih 100 zahteva, trebalo je da vidimo oko jedan zahtev sa kašnjenjem od 50 ms.

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

Dakle, kašnjenje nije uzrokovao moj kod, gRPC implementacija ili mreža. Počeo sam da brinem da ovo nikada neću razumeti.

Sad na kom OS-u smo

gRPC široko se koristi na Linuxu, ali egzotično na Windowsima. Odlučio sam isprobati eksperiment, koji je uspio: kreirao sam Linux virtuelnu mašinu, kompajlirao Alvin za Linux i postavio ga.

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

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

Nagleov algoritam

Sve ovo vrijeme sam mislio da mi nedostaje zastava gRPC. Sada shvatam šta je to zaista gRPC Windows zastavica nedostaje. Našao sam internu RPC biblioteku za koju sam bio siguran da će dobro raditi za sve postavljene zastavice Winsock. Zatim sam dodao sve ove zastavice u gRPC i postavio Alvina na Windows, u zakrpljenom Windows ping-pong serveru!

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

Skoro Gotovo: Počeo sam da uklanjam dodane zastavice jednu po jednu dok se regresija ne vrati kako bih mogao precizno odrediti uzrok. Bilo je neslavno TCP_NODELAY, Nagleov algoritamski prekidač.

Nagleov algoritam pokušava smanjiti broj paketa poslatih preko mreže odgađanjem prijenosa poruka sve dok veličina paketa ne pređe određeni broj bajtova. Iako bi ovo moglo biti lijepo za prosječnog korisnika, destruktivno je za servere u realnom vremenu jer će OS odgoditi neke poruke, uzrokujući kašnjenje na niskom QPS-u. U gRPC ova zastavica je postavljena u Linux implementaciji za TCP utičnice, ali ne i u Windowsu. Ja sam ovo ispravljeno.

zaključak

Veća latencija pri niskom QPS bila je uzrokovana optimizacijom OS-a. Retrospektivno, profiliranje nije otkrilo kašnjenje jer je urađeno u kernel modu, a ne u korisnički način rada. Ne znam da li se Nagleov algoritam može posmatrati kroz ETW snimke, ali bi bilo zanimljivo.

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

Sljedeći put kada vidite povećanje latencije kako se broj zahtjeva u sekundi smanjuje, Nagleov algoritam bi trebao biti na vašoj listi osumnjičenih!

izvor: www.habr.com

Dodajte komentar