Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

Stejně jako v většině příspěvků, objevila se problém s distribuovanou službou, nazvěme tuto službu Elvin. Tentokrát jsem problém neobjevil sám, dali mi o něm vědět kluci z klientské části.

Jednou jsem se probudil od nespokojeného e-mailu kvůli velkým zpožděním u Elvina, kterého jsme plánovali zavést v blízké době. Zejména se klient setkal se zpožděním v 99. percentilu kolem 50 ms, což bylo mnohem nad naším rozpočtem pro zpoždění. Bylo to překvapivé, protože jsem službu pečlivě testoval, zejména na latenci, neboť to je předmět častých stížností.

Než jsem dal Elvina do testování, provedl jsem mnoho experimentů s 40 000 dotazy za sekundu (QPS), všechny ukázaly latenci pod 10 ms. Byl jsem připraven prohlásit, že s jejich výsledky nesouhlasím. Ale když jsem se znovu podíval na e-mail, všiml jsem si něčeho nového: přesně jsem netestoval podmínky, které zmínili, jejich QPS bylo mnohem nižší než moje. Testoval jsem na 40k QPS, a oni pouze na 1k. Spustil jsem další experiment, tentokrát s nižším QPS, jen abych je uklidnil.

Protože o tom píšu na blogu – pravděpodobně už jste pochopili: jejich čísla se ukázala jako správná. Kontroloval jsem svého virtuálního klienta znovu a znovu, stále se stejným výsledkem: nízký počet dotazů nezvyšuje pouze latenci, ale zvyšuje počet dotazů se zpožděním nad 10 ms. Jinými slovy, zatímco při 40k QPS přibližně 50 dotazů za sekundu překročilo 50 ms, při 1k QPS každou sekundu bylo 100 dotazů nad 50 ms. Paradox!

Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

Zúžení okruhu podezřelých.

Při čelní problému zpoždění v distribuovaném systému s mnoha komponenty je prvním krokem sestavit krátký seznam podezřelých. Pojďme se podívat hlouběji do architektury Elvina:

Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

Dobrou výchozí bodem je seznam provedených přechodů vstupů a výstupů (síťové volání/hledání na disku atd.). Pokusíme se zjistit, kde je zpoždění. Kromě zřejmého vstupu a výstupu s klientem, Elvin provádí další krok: obrací se na úložiště dat. Ale toto úložiště funguje ve stejném clusteru jako Elvin, takže by tam zpoždění mělo být menší než s klientem. Takže seznam podezřelých:

  1. Síťové volání od klienta k Elvinovi.
  2. Síťové volání od Elvina k datovému úložišti.
  3. Hledání na disku v datovém úložišti.
  4. Síťové volání z datového úložiště k Elvinovi.
  5. Síťové volání od Elvina k klientovi.

Pokud zkusíme vyjmout některé položky.

Datové úložiště není příčinou.

Nejprve jsem převedl Elvina na ping-ping server, který nezpracovává žádné požadavky. Po obdržení požadavku vrátí prázdnou odpověď. Pokud se zpoždění zkracuje, pak je chyba v implementaci Elvina nebo datového úložiště — nic takového neslýchaného. V prvním experimentu máme takový graf:

Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

Jak vidíme, při použití ping-ping serveru se neobjevují žádná zlepšení. To znamená, že datové úložiště nezvyšuje zpoždění, a seznam podezřelých se zúžil na polovinu:

  1. Síťové volání od klienta k Elvinovi.
  2. Síťové volání od Elvina k klientovi.

Skvělé! Seznam se rychle zúžuje. Myslel jsem, že jsem téměř objevil příčinu.

gRPC

Právě teď je čas představit vám nového hráče: gRPC. Je to knihovna s otevřeným zdrojovým kódem od Google pro intraprocesní komunikaci. RPC. I když gRPC je dobře optimalizovaný a široce používaný, poprvé jsem jej použil v systému takového rozsahu a očekával jsem, že moje implementace bude méně efektivní — mírně řečeno.

Přítomnost gRPC ve stacku vyvolala novou otázku: možná je to moje implementace nebo sám gRPC způsobuje problém se zpožděním? Přidáváme do seznamu nového podezřelého:

  1. Klient volá knihovnu gRPC
  2. Knihovna gRPC na klientovi provádí síťové volání knihovny gRPC na serveru
  3. Knihovna gRPC se obrací na Elvina (operace není v případě serveru ping-pong)

Abychom vám ukázali, jak vypadá kód, moje implementace klienta/Elvina se moc neliší od příkladů klient-server asynchronních.

Poznámka: výše uvedený seznam je trochu zjednodušen, protože gRPC umožňuje použití vlastní (šablonové?) modelové osnovy, která propletuje běhový stack gRPC a uživatelskou implementaci. Pro jednoduchost se budeme držet tohoto modelu.

Profilování to všechno opraví.

Po vyřazení datových úložišť jsem si myslel, že jsem skoro hotov: 'Teď to bude snadné! Použijeme profil a zjistíme, kde vzniká zpoždění.' Jsem velkým fanouškem přesného profilování,protože CPU je velmi rychlé a často není úzkým místem. Většina zpoždění se vyskytuje, když procesor musí zastavit zpracování, aby udělal něco jiného. Přesné profilování CPU je určeno právě k tomuto: přesně zaznamenává vše. kontextové přepínače a naznačuje, kde dochází k prodlevám.

Vzal jsem čtyři profily: jeden pro vysoký QPS (malá prodleva) a s ping-pong serverem na nízkém QPS (velká prodleva) jak na straně klienta, tak na straně serveru. A pro jistotu jsem si také vzal vzorek profilu CPU. Při porovnávání profilů obvykle hledám anomální stack volání. Například na špatné straně s vysokou prodlevou dochází k mnohem více kontextovým přepnutím (10 a vícekrát). Ale v mém případě se počet kontextových přepnutí prakticky shodoval. K mému zděšení tam nebylo nic zásadního.

Další ladění

Byl jsem zoufalý. Nevěděl jsem, jaké další nástroje mohu použít, a můj další plán v zásadě spočíval v opakování experimentů s různými variantami, nikoli v jasném diagnostikování problému.

Co když

Už od začátku mě znepokojoval specifický čas prodlevy 50 ms. To je velmi dlouhá doba. Rozhodl jsem se, že budu odřezávat kousky kódu, dokud nezjistím, která část způsobuje tuto chybu. Poté následoval experiment, který vyšel.

Jak už to bývá, zpětně to vypadá, že všechno bylo jasné. Dal jsem klienta na jeden stroj s Elvinem – a poslal žádost na localhost. A prodleva se vytratila!

Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

Něco nebylo v pořádku sítě.

Osvojení si dovedností síťového inženýra

Musím přiznat: moje znalosti sítí jsou hrozné, zvlášť když s nimi pracuji každý den. Ale síť byla hlavním podezřelým a musel jsem se naučit, jak ji ladit.

Naštěstí internet miluje ty, kteří se chtějí učit. Kombinace ping a tracert se zdála jako dostatečně dobrý začátek pro ladění problémů se síťovým transportem.

Nejprve jsem spustil PsPing na TCP portu Elvina. Použil jsem výchozí parametry – nic zvláštního. Z více než tisíce pingů žádný nepřekročil 10 ms, s výjimkou prvního pro zahřátí. To je v rozporu se zjevným zvýšením prodlevy 50 ms v 99. percentilu: tam jsme měli vidět přibližně jeden požadavek na každých 100 s prodlevou 50 ms.

Pak jsem zkusil tracert: možná je problém na jednom z uzlů na trase mezi Elvinem a klientem. Ale ani tracer se nevrátil s prázdnou.

Takže důvodem zpoždění nebyl můj kód, implementace gRPC ani síť. Začal jsem se už obávat, že tomu nikdy neporozumím.

Teď, na jakém operačním systému se nacházíme

gRPC široce se používá v Linuxu, ale pro Windows je to exotika. Rozhodl jsem se provést experiment, který vyšel: vytvořil jsem virtuální stroj Linux, zkompiloval Elvina pro Linux a nasadil ho.

Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

A takto to dopadlo: na ping-pong serveru Linux nebyla taková zpoždění jako u srovnatelného uzlu Windows, i když zdroj dat byl stejný. Zjistil jsem, že problém je v implementaci gRPC pro Windows.

Näherův algoritmus

Celou dobu jsem si myslel, že mi chybí příznak gRPC. Teď jsem pochopil, že ve skutečnosti jde o gRPC chybějící příznak Windows. Našel jsem vnitřní knihovnu RPC, u které jsem byl přesvědčen, že funguje dobře pro všechny nainstalované příznaky Winsock. Pak jsem přidal všechny tyto příznaky do gRPC a nasadil Elvina na Windows, v opraveném ping-pong serveru pod Windows!

Někdy víc znamená méně. Když snížení zátěže vede k zvýšení latence.

Téměř hotovo: začal jsem odebírat přidané příznaky jeden po druhém, dokud se nezapnula regresní chyba, takže jsem mohl přesně určit její příčinu. Byl to notoricky známý TCP_NODELAY, přepínač Nährungsova algoritmu.

Näherův algoritmus se snaží snížit počet paketů odeslaných přes síť tím, že zpožďuje odesílání zpráv, dokud velikost paketu nepřekročí určité množství bajtů. I když to může být příjemné pro průměrného uživatele, je to devastující pro servery v reálném čase, protože OS bude zpožďovat některé zprávy, což způsobí zpoždění při nízkém QPS. U gRPC byl tento příznak nastaven v implementaci Linux pro TCP sokety, ale ne pro Windows. Opravil jsem to. Velké zpoždění při nízkém QPS bylo způsobeno optimalizací OS. S ohledem na zpětnou vazbu, profilování nezjistilo zpoždění, protože bylo prováděno v jádrovém režimu, nikoli v.

Závěr

uživatelském režimu. Nevím, zda lze Nährungsův algoritmus pozorovat pomocí ETW snímků, ale bylo by to zajímavé.Pokud jde o experiment localhost, ten pravděpodobně nesouvisel s vlastním síťovým kódem, a Nährungsův algoritmus nebyl spuštěn, takže problémy se zpožděním zmizely, když se klient obrátil na Elvina přes localhost.

Příště, když uvidíte zvýšení zpoždění při snižování počtu požadavků za sekundu, měl by být Nährungsův algoritmus na vašem seznamu podezřelých!

!

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster