Steven
Steven5 min čítania

Deň, keď naša aplikácia DDoSovala samú seba

Nahromadené čakajúce uploady, vypustené naraz pri štarte, premenili každého klienta GeekBye na malý denial-of-service útok na naše vlastné servery. Oprava — a rebrík liveness spojenia, ktorý si vynútila — patrí k najužitočnejším veciam, ktoré nás v2 naučila.

Spoľahlivosť
Siete
Inžinierstvo
Vydania GeekBye
Deň, keď naša aplikácia DDoSovala samú seba

Každý inžinier distribuovaných systémov skôr či neskôr stretne thundering herd (splašené stádo): masu klientov, ktorí robia to isté v tom istom okamihu, a zdieľaný zdroj za nimi sa prehýba. Zvyčajne sú to niekoho cudzí klienti. V GeekBye v2.0.1 to boli naši a útočníkom bola naša vlastná aplikácia.

Ako zapisovač útočí sám na seba

GeekBye vie nahrávať záznamy na váš Google Drive. Ak sa záznam nedá nahrať okamžite — boli ste offline, aplikácia sa zavrela, Drive sa zasekol — putuje do fronty, aby to skúsil znova neskôr. Rozumné.

Zlyhanie bolo v tom, kedy nastalo to „neskôr". Pri ďalšom spustení sa aplikácia pokúsila vyprázdniť celú frontu naraz. Jeden používateľ s týždňom čakajúcich záznamov sa stal návalom súbežných požiadaviek na upload v okamihu, keď otvoril aplikáciu. Vynásobte to každým používateľom, ktorý ráno spúšťa aplikáciu, a náš backend mal autentifikovať, skontrolovať limity a spracovať stenu premávky v tých istých pár sekundách — od klientov, ktorí sa všetci, technicky, správali správne.

Náš backend urobil správnu vec a tento záplav obmedzil (rate-limit). A tu sa začala škoda druhého rádu. Odpoveď o prekročení limitu je HTTP 429 a 429 vyzerá veľmi podobne ako iné zlyhania, ak nie ste opatrní:

  • Štartové načítanie profilu dostalo 429 a aplikácia to považovala za neúspešnú autentifikáciu — nakrátko odhlásila používateľov z úplne platnej relácie.
  • Spojenie transkripcie dostalo generické 429 a zobrazilo výzvu na upgrade pre vyčerpanie limitu audia — hovoriac platiacim používateľom, že dosiahli kvótu, ktorú nedosiahli.

Takže jedna hlavná príčina — nezvládnutá fronta — vyprodukovala tri viditeľné príznaky: záťaž servera, falošné odhlásenia a falošné upselly. Klasický tvar self-DoS: záťaž je zlá, ale to, čo používatelia skutočne cítia, je nesprávny výklad príznakov tej záťaže.

Oprava, v dvoch vrstvách

Zastav paniku stáda. Fronta uploadov je teraz rozložená v čase — požiadavky rozptýlené s backoffom a po každom 429 nasleduje cooldown, aby klient ustúpil od zaťaženého servera, namiesto toho aby naň naliehal viac. Thundering herd sa mení na usporiadanú frontu.

Zastav nesprávny výklad. Prechodné 429 alebo 5xx počas štartu vás už neodhlási — klient rozlišuje „server je nakrátko zaneprázdnený" od „vaša relácia je neplatná". A generické 429 o prekročení limitu na ceste transkripcie sa už nevydáva za chybu vyčerpania kvóty audia; iba skutočná odpoveď o kvóte zobrazí výzvu na upgrade. Ponaučenie, ktoré zostalo: kód chyby nie je význam chyby. 429 znamená „spomaľ", nie „nie si autorizovaný" a nie „minula sa ti kvóta" — a klient musí poznať ten rozdiel.

Rebrík liveness, ktorý nasledoval

Zvládnutie stáda odhalilo tichší problém: keď sa spojenie naozaj pokazilo pod záťažou, ako rýchlo sme si to všimli? Pomalšie, než sme chceli. Takže v2.0.4 vybudoval rebrík liveness spojenia pre transkripciu v reálnom čase:

  • Heartbeaty — socket transkripcie sa pinguje v pevnom intervale, takže potichu mŕtve spojenie sa zistí v sekundách namiesto čakania, kým zlyhá ďalší kus audia.
  • Kopanec opätovného pripojenia pri návrate siete — keď OS ohlási, že sieť je späť, aplikácia proaktívne obnoví spojenie namiesto toho, aby čakala, kým na to odhalenie narazí.
  • Liveness cez kontrolné ping/pong — round-trip na úrovni aplikácie, ktorý potvrdzuje nielen „socket je otvorený", ale „druhý koniec naozaj odpovedá".
  • Brána opätovného pripojenia patriaca službe — jedno miesto rozhoduje, či sa pripojiť znova, namiesto niekoľkých častí aplikácie, ktoré sa o to pretekajú a šliapu si navzájom po nohách.

Nič z toho nie je efektné. Všetko to je dôvod, prečo relácia vo v2 pôsobí, akoby jednoducho... zostala pripojená.

Stalo sa to znova tento týždeň — o poschodie nižšie

Tu je časť, ktorá z toho robí viac než len vojnovú historku. Ten istý druh zlyhania sa vynoril pred pár dňami, o úroveň pod nami. Jediný klient vypálil 21 štartov relácie transkripcie za menej než 400 milisekúnd — a zakopol o limit nášho poskytovateľa reči platný pre celé konto: 15 súbežných požiadaviek. Nával stáda proti zdieľanej infraštruktúre, presne ako fronta uploadov, len namierený na závislosť, nie na náš vlastný backend.

Tvar je identický a oprava tiež: naliehajúci klient potrebuje single-flight guard, aby nemohol vypáliť dvadsať štartov na jeden zámer, a zdieľaný zdroj potrebuje limit na používateľa, aby jeden klient nemohol pohltiť celý pool. Klientsku verziu tohto sme už postavili — pacer uploadov je tento vzor. Teraz ho aplikujeme na štarty relácií. Thundering herd nie je bug, ktorý opravíš raz; je to tvar, ktorý sa naučíš rozpoznávať všade tam, kde klienti narážajú na zdieľaný limit.

Tri veci na zapamätanie

  1. Vaši vlastní klienti sú záťažový test, ktorý ste nenaplánovali. Čokoľvek, čo dávkuje-pri-štarte, opakuje-pri-spustení alebo sa pripája-pri-prebudení, je thundering herd čakajúci na dosť používateľov. Rozložte to v čase skôr, než ich budete mať.
  2. Rozlišujte kód od významu. 429 je najčastejšie nesprávne čítaný stav, aký existuje. „Spomaľ" nie je „odhlás sa" a nie je „zaplať nám". Nasmerujte každé zlyhanie k tomu, čo naozaj znamená.
  3. Liveness je rebrík, nie vlajka. „Žije spojenie?" má niekoľko čestných odpovedí na rôznych vrstvách — socket otvorený, bajty tečú, druhý koniec odpovedá, sieť prítomná. Robustná aplikácia kontroluje viac než jednu.

Toto je tá neefektná mašinéria pod pokojom GeekBye v2. O funkciách spoľahlivosti, ktoré umožnila, si prečítajte prečo váš AI zapisovač zlyháva na zlom Wi-Fi a prepis naživo, keď firewall blokuje WebSockety (v2.0.8). O susedných vydaniach tejto série — prečo váš AI zapisovač prestane nahrávať uprostred stretnutia (v2.0.9).