Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

Mint a a legtöbb hozzászólás, probléma van egy elosztott szolgáltatással, nevezzük ezt a szolgáltatást Alvinnak. Ezúttal nem magam fedeztem fel a problémát, tájékoztattak az ügyféloldali srácok.

Egy nap egy elégedetlen e-mailre ébredtem az Alvinnal való hosszú késések miatt, amelyet a közeljövőben terveztünk elindítani. Pontosabban, az ügyfél a 99. percentilis késleltetést 50 ms körüli tartományban tapasztalta, ami jóval meghaladja a várakozási időkeretünket. Ez meglepő volt, mivel alaposan teszteltem a szolgáltatást, különösen a késleltetést illetően, ami gyakori panasz.

Mielőtt az Alvint teszteltem volna, sok kísérletet futtattam 40 10 lekérdezéssel másodpercenként (QPS), amelyek mindegyike 40 ms-nál kevesebb késleltetést mutatott. Kész voltam kijelenteni, hogy nem értek egyet az eredményeikkel. Ám ha még egyszer megnézem a levelet, valami újat vettem észre: nem teszteltem pontosan az általuk említett körülményeket, a QPS-jük jóval alacsonyabb volt, mint az enyém. Én 1 ezer QPS-en teszteltem, de ők csak XNUMX ezernél. Futtattam egy másik kísérletet, ezúttal alacsonyabb QPS-sel, hogy megnyugtassam őket.

Mióta blogolok erről, valószínűleg már rájöttél, hogy a számuk helyes. Újra és újra teszteltem a virtuális kliensemet, ugyanazzal az eredménnyel: az alacsony kérések száma nemcsak a késleltetést növeli, hanem a 10 ms-nál hosszabb késleltetésű kérések számát is. Más szóval, ha 40k QPS-nél körülbelül 50 kérés másodpercenként haladta meg az 50 ms-ot, akkor 1k QPS-nél másodpercenként 100 kérés volt 50 ms felett. Paradoxon!

Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

A keresés szűkítése

Ha késleltetési problémával szembesül egy sok összetevőt tartalmazó elosztott rendszerben, az első lépés a gyanúsítottak rövid listájának létrehozása. Nézzünk egy kicsit mélyebbre Alvin építészetében:

Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

Jó kiindulópont a befejezett I/O átmenetek listája (hálózati hívások/lemezkeresések stb.). Próbáljuk kitalálni, hol van a késés. A klienssel való nyilvánvaló I/O mellett Alvin egy további lépést tesz: hozzáfér az adattárhoz. Ez a tároló azonban ugyanabban a fürtben működik, mint az Alvin, így a késleltetésnek kisebbnek kell lennie, mint az ügyfélnél. Tehát a gyanúsítottak listája:

  1. Hálózati hívás az ügyféltől Alvin felé.
  2. Hálózati hívás Alvintól az adattárba.
  3. Keresés a lemezen az adattárban.
  4. Hálózati hívás az adattárházból Alvinhoz.
  5. Hálózati hívás Alvintól egy ügyfélhez.

Próbáljunk meg néhány pontot áthúzni.

Az adattárolásnak ehhez semmi köze

Az első dolgom az volt, hogy az Alvint ping-ping szerverré alakítottam, amely nem dolgozza fel a kéréseket. Amikor kérést kap, üres választ ad vissza. Ha a várakozási idő csökken, akkor az Alvin vagy az adattárház megvalósításának hibája nem hallatlan. Az első kísérletben a következő grafikont kapjuk:

Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

Mint látható, a ping-ping szerver használatakor nincs javulás. Ez azt jelenti, hogy az adattárház nem növeli a késleltetést, és a gyanúsítottak listája felére csökken:

  1. Hálózati hívás az ügyféltől Alvin felé.
  2. Hálózati hívás Alvintól egy ügyfélhez.

Nagy! A lista gyorsan zsugorodik. Azt hittem, már majdnem rájöttem az okára.

gRPC

Itt az ideje, hogy bemutassunk egy új játékost: gRPC. Ez a Google nyílt forráskódú könyvtára a folyamat közbeni kommunikációhoz RPC. bár gRPC jól optimalizált és széles körben használt, ez volt az első alkalom, hogy ekkora rendszeren használtam, és arra számítottam, hogy a megvalósításom enyhén szólva nem lesz optimális.

elérhetőség gRPC a veremben egy új kérdés merült fel: talán az én megvalósításom vagy én magam gRPC késleltetési problémát okoz? Új gyanúsított felvétele a listára:

  1. Az ügyfél felhívja a könyvtárat gRPC
  2. könyvtár gRPC hálózati hívást kezdeményez a kliens könyvtárába gRPC a szerveren
  3. könyvtár gRPC kapcsolattartó Alvin (ping-pong szerver esetén nincs művelet)

Hogy képet adjunk arról, hogyan néz ki a kód, az én kliens/Alvin implementációm nem sokban különbözik a kliens-szervertől aszinkron példák.

Megjegyzés: A fenti lista kissé leegyszerűsített, mert gRPC lehetővé teszi a saját (sablon?) szálfűzési modell használatát, amelyben a végrehajtási verem összefonódik gRPC és a felhasználói megvalósítás. Az egyszerűség kedvéért maradunk ennél a modellnél.

A profilozás mindent megold

Miután áthúztam az adattárakat, azt hittem, már majdnem kész vagyok: „Most már könnyű! Alkalmazzuk a profilt, és derítsük ki, hol van a késés.” én a precíziós profilalkotás nagy rajongója, mert a CPU-k nagyon gyorsak, és legtöbbször nem jelentik a szűk keresztmetszetet. A legtöbb késés akkor fordul elő, amikor a processzornak le kell állítania a feldolgozást, hogy valami mást tegyen. Az Accuracy CPU Profiling pont ezt teszi: mindent pontosan rögzít kontextuskapcsolók és egyértelművé teszi, hol fordulnak elő késések.

Négy profilt választottam: magas QPS-sel (alacsony késleltetéssel) és egy ping-pong szerverrel alacsony QPS-sel (magas késleltetéssel), mind a kliens, mind a szerver oldalon. És minden esetre vettem egy minta processzorprofilt is. A profilok összehasonlításakor általában egy rendellenes hívási veremre figyelek. Például a magas késleltetésű rossz oldalon sokkal több környezeti kapcsoló van (10-szer vagy többször). De az én esetemben a kontextusváltások száma majdnem ugyanannyi volt. Rémületemre nem volt ott semmi jelentős.

További hibakeresés

kétségbeesett voltam. Nem tudtam, milyen egyéb eszközöket használhatok, és a következő tervem lényegében az volt, hogy megismételjem a kísérleteket különböző változatokkal, ahelyett, hogy egyértelműen diagnosztizálnám a problémát.

Mi van ha

Kezdettől fogva aggódtam a konkrét 50 ms-os késleltetés miatt. Ez egy nagyon nagy idő. Úgy döntöttem, hogy darabokat vágok ki a kódból, amíg ki nem találom, hogy pontosan melyik rész okozza ezt a hibát. Aztán jött egy kísérlet, ami bevált.

Mint általában, utólag úgy tűnik, minden nyilvánvaló volt. A klienst ugyanarra a gépre helyeztem, mint az Alvint – és elküldtem egy kérést localhost. És a látencia növekedése elmúlt!

Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

Valami nem stimmelt a hálózattal.

Hálózatmérnöki ismeretek elsajátítása

Be kell vallanom: a hálózati technológiákkal kapcsolatos tudásom borzasztó, főleg, ha figyelembe vesszük, hogy minden nap dolgozom velük. De a hálózat volt az elsődleges gyanúsított, és meg kellett tanulnom a hibakeresést.

Szerencsére az internet szereti a tanulni vágyókat. A ping és a tracert kombinációja elég jó kezdetnek tűnt a hálózati szállítási problémák hibakereséséhez.

Először is elindítottam PsPing Alvin TCP portjára. Az alapértelmezett beállításokat használtam - semmi különös. A több mint ezer ping közül egyik sem haladta meg a 10 ms-t, kivéve az elsőt a bemelegítésnél. Ez ellentétes a 50. percentilisnél megfigyelt 99 ms-os késleltetés növekedésével: ott minden 100 kérés után körülbelül egy kérést kellett volna látnunk 50 ms-os késleltetéssel.

Aztán megpróbáltam tracert: Probléma lehet az Alvin és az ügyfél közötti útvonal egyik csomópontjában. De a nyomkövető is üres kézzel tért vissza.

Tehát nem az én kódom, a gRPC implementáció vagy a hálózat okozta a késést. Kezdtem aggódni, hogy ezt soha nem fogom megérteni.

Most milyen operációs rendszert használunk

gRPC széles körben használt Linuxon, de egzotikus Windowson. Úgy döntöttem, kipróbálok egy kísérletet, ami bevált: létrehoztam egy Linux virtuális gépet, lefordítottam az Alvint Linuxra, és telepítettem.

Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

És a következő történt: a Linux ping-pong szerveren nem voltak olyan késések, mint egy hasonló Windows gazdagépen, bár az adatforrás nem különbözött egymástól. Kiderült, hogy a probléma a Windows gRPC megvalósításában van.

Nagle algoritmusa

Egész idő alatt azt hittem, hiányzik egy zászló gRPC. Most már értem, mi is ez valójában gRPC A Windows zászló hiányzik. Találtam egy belső RPC-könyvtárat, amelyről meg voltam győződve, hogy minden jelzőkészlethez jól fog működni Megnyerő. Aztán hozzáadtam ezeket a jelzőket a gRPC-hez, és telepítettem az Alvint Windowson, egy javított Windows ping-pong szerveren!

Néha a több kevesebb. A terhelés csökkentése a késleltetés növekedését eredményezi

csaknem Kész: Elkezdtem egyenként eltávolítani a hozzáadott jelzőket, amíg a regresszió vissza nem tért, hogy pontosan meghatározhassam az okot. Hírhedt volt TCP_NODELAY, Nagle algoritmus kapcsolója.

Nagle algoritmusa megpróbálja csökkenteni a hálózaton keresztül elküldött csomagok számát az üzenetek továbbításának késleltetésével, amíg a csomag mérete meg nem halad egy bizonyos számú bájtot. Bár ez kellemes lehet az átlagfelhasználók számára, romboló hatású a valós idejű szerverek számára, mivel az operációs rendszer késlelteti az üzeneteket, ami késéseket okoz az alacsony QPS-nél. U gRPC ez a jelző a TCP socketekhez való Linux-megvalósításban be van állítva, de a Windowsban nem. én vagyok ez javítva.

Következtetés

Az alacsony QPS melletti magasabb késleltetést az operációs rendszer optimalizálása okozta. Utólag visszatekintve a profilalkotás nem észlelte a késleltetést, mert kernel módban történt, nem pedig benne felhasználói mód. Nem tudom, hogy a Nagle algoritmusa megfigyelhető-e ETW rögzítéseken keresztül, de érdekes lenne.

Ami a localhost kísérletet illeti, valószínűleg nem érintette a tényleges hálózati kódot, és a Nagle algoritmusa nem futott, így a késleltetési problémák megszűntek, amikor a kliens a localhost-on keresztül elérte az Alvint.

A következő alkalommal, amikor a várakozási idő növekedését tapasztalja, ahogy a másodpercenkénti kérések száma csökken, a Nagle algoritmusának szerepelnie kell a gyanúsítottak listáján!

Forrás: will.com

Hozzászólás