Steven
Steven5 min. skaitymo

Diena, kai mūsų programa surengė DDoS pati sau

Susikaupusių laukiančių įkėlimų eilė, paleista visa iš karto paleidimo metu, kiekvieną GeekBye klientą pavertė nedidele denial-of-service ataka prieš mūsų pačių serverius. Pataisa — ir ryšio liveness kopėčios, kurias ji privertė mus pastatyti — vienas naudingiausių dalykų, kurių išmokė v2.

Patikimumas
Tinklai
Inžinerija
GeekBye leidimai
Diena, kai mūsų programa surengė DDoS pati sau

Kiekvienas paskirstytų sistemų inžinierius anksčiau ar vėliau susitinka thundering herd (išsigandusią bandą): daugybę klientų, darančių tą patį tą pačią akimirką, o bendras išteklius už jų linksta. Paprastai tai kažkieno svetimi klientai. GeekBye v2.0.1 tai buvo mūsų, o užpuolikas buvo mūsų pačių programa.

Kaip užrašinė puola pati save

GeekBye moka įkelti įrašus į jūsų Google Drive. Jei įrašo nepavyksta įkelti iš karto — buvote neprisijungę, programa užsidarė, Drive užspringo — jis patenka į eilę, kad būtų pakartota vėliau. Protinga.

Gedimas buvo tame, kada ateidavo tas „vėliau". Kito paleidimo metu programa bandydavo ištuštinti visą eilę iš karto. Vienas naudotojas su savaitės laukiančių įrašų atsarga tapdavo vienalaikių įkėlimo užklausų pliūpsniu tą akimirką, kai atverdavo programą. Padauginkite iš kiekvieno naudotojo, ryte paleidžiančio programą, ir mūsų backend buvo prašomas autentifikuoti, patikrinti limitus ir apdoroti srauto sieną per tas pačias kelias sekundes — iš klientų, kurie visi, techniškai, elgėsi teisingai.

Mūsų backend padarė teisingą dalyką ir šį antplūdį apribojo (rate-limit). Ir čia prasidėjo antrinė žala. Atsakas apie viršytą limitą yra HTTP 429, o 429 labai panašus į kitus gedimus, jei nesate atsargūs:

  • Paleidimo profilio gavimas gavo 429, ir programa palaikė tai nepavykusia autentifikacija — trumpam atjungdama naudotojus iš visiškai galiojančios sesijos.
  • Transkripcijos ryšys gavo bendrinį 429 ir parodė raginimą atnaujinti dėl išnaudoto garso limito — sakydama mokantiems naudotojams, kad jie pasiekė kvotą, kurios nepasiekė.

Taigi viena pagrindinė priežastis — nesuvaldyta eilė — davė tris matomus simptomus: serverio apkrovą, klaidingus atsijungimus ir klaidingus upsell. Klasikinė self-DoS forma: apkrova yra bloga, bet būtent klaidingas apkrovos simptomų aiškinimas yra tai, ką naudotojai iš tikrųjų jaučia.

Pataisa, dviem sluoksniais

Sustabdyk bandos paniką. Įkėlimų eilė dabar išdėstyta laike — užklausos paskleistos su backoff, o po bet kurio 429 seka cooldown, kad klientas trauktųsi nuo apkrauto serverio, o ne spaustų jį dar labiau. Thundering herd tampa tvarkinga eile.

Sustabdyk klaidingą aiškinimą. Pereinantis 429 ar 5xx paleidimo metu jūsų nebeatjungia — klientas skiria „serveris trumpam užimtas" nuo „jūsų sesija negalioja". O bendrinis 429 apie viršytą limitą transkripcijos kelyje nebeapsimeta garso kvotos išnaudojimo klaida; tik tikras kvotos atsakas parodo raginimą atnaujinti. Pamoka, kuri įstrigo: klaidos kodas nėra klaidos reikšmė. 429 reiškia „sulėtink", o ne „tu neautorizuotas" ir ne „tavo kvota baigėsi" — ir klientas privalo žinoti skirtumą.

Liveness kopėčios, atėjusios po to

Bandos suvaldymas atskleidė tylesnę problemą: kai ryšys iš tikrųjų sugesdavo po apkrova, kaip greitai tai pastebėdavome? Lėčiau, nei norėjome. Tad v2.0.4 pastatė ryšio liveness kopėčias transkripcijai realiu laiku:

  • Heartbeat — transkripcijos soketas pinguojamas fiksuotu intervalu, tad tyliai miręs ryšys aptinkamas per sekundes, o ne laukiant, kol sugęs kitas garso gabalas.
  • Pakartotinio prisijungimo spyris grįžus tinklui — kai OS praneša, kad tinklas grįžo, programa proaktyviai atkuria ryšį, o ne laukia, kol užklius už šio atradimo.
  • Liveness per kontrolinį ping/pong — programos lygio round-trip, patvirtinantis ne tik „soketas atviras", bet „kitas galas iš tikrųjų atsako".
  • Paslaugai priklausantys pakartotinio prisijungimo vartai — viena vieta sprendžia, ar prisijungti iš naujo, o ne kelios programos dalys, kurios lenktyniauja tai daryti ir lipa viena kitai ant kojų.

Nieko iš to nėra efektinga. Visa tai — priežastis, kodėl sesija v2 jaučiasi taip, tarsi ji tiesiog... lieka prisijungusi.

Tai nutiko vėl šią savaitę — aukštu žemiau

Štai dalis, kuri iš to daro daugiau nei karo istoriją. Tas pats gedimo tipas vėl iškilo prieš kelias dienas, lygiu žemiau mūsų. Vienas klientas iššovė 21 transkripcijos sesijos startą per mažiau nei 400 milisekundžių — ir užkliuvo už visai paskyrai galiojančio mūsų kalbos tiekėjo limito: 15 vienalaikių užklausų. Bandos antplūdis prieš bendrą infrastruktūrą, lygiai kaip įkėlimų eilė, tik nutaikytas į priklausomybę, o ne į mūsų pačių backend.

Forma identiška, ir pataisa taip pat: puolančiam klientui reikia single-flight guard, kad jis negalėtų iššauti dvidešimties startų vienam ketinimui, o bendram ištekliui reikia limito vienam naudotojui, kad vienas klientas negalėtų suryti viso pool. Kliento pusės versiją to jau esame pastatę — įkėlimų pacer ir yra šis šablonas. Dabar taikome jį sesijų startams. Thundering herd nėra klaida, kurią pataisai kartą; tai forma, kurią išmoksti atpažinti visur, kur klientai susiduria su bendru limitu.

Trys dalykai išsinešti

  1. Jūsų pačių klientai — tai apkrovos testas, kurio neplanavote. Bet kas, kas grupuoja-paleidžiant, kartoja-startuojant ar prisijungia-iš-naujo-pabudus, yra thundering herd, laukiantis pakankamai naudotojų. Išdėstykite tai laike, kol jų dar neturite.
  2. Skirkite kodą nuo reikšmės. 429 — labiausiai klaidingai skaitomas statusas pasaulyje. „Sulėtink" nėra „atsijunk" ir nėra „sumokėk mums". Nukreipkite kiekvieną gedimą į tai, ką jis iš tikrųjų reiškia.
  3. Liveness — tai kopėčios, o ne vėliavėlė. „Ar ryšys gyvas?" turi kelis sąžiningus atsakymus skirtinguose sluoksniuose — soketas atviras, baitai teka, kitas galas atsako, tinklas yra. Patikima programa tikrina daugiau nei vieną.

Tai ta neefektinga mašinerija po GeekBye v2 ramybe. Apie patikimumo funkcijas, kurias ji įgalino, skaitykite kodėl jūsų AI užrašinė sustoja prastame Wi-Fi ir tiesioginė transkripcija, kai ugniasienė blokuoja WebSocket (v2.0.8). Apie gretimus šios serijos leidimus — kodėl jūsų AI užrašinė nustoja įrašinėti vidury susitikimo (v2.0.9).