Steven
Steven5 perc olvasás

A nap, amikor az appunk saját magát DDoS-olta

Egy backlognyi függőben lévő feltöltés, indításkor egyszerre elengedve, minden GeekBye klienst egy kis denial-of-service támadássá változtatott a saját szervereink ellen. A javítás — és a connection-liveness létra, amelynek megépítésére kényszerített — az egyik leghasznosabb dolog, amit a v2 tanított nekünk.

Megbízhatóság
Hálózatok
Mérnökség
GeekBye kiadások
A nap, amikor az appunk saját magát DDoS-olta

Minden elosztott rendszerekkel foglalkozó mérnök előbb-utóbb találkozik a thundering herddel (a nekiiramodó csordával): kliensek tömege, mind ugyanazt csinálja ugyanabban a pillanatban, és a mögöttük lévő közös erőforrás beroskad. Általában valaki más kliensei. A GeekBye v2.0.1-ben a mieink voltak, és a támadó a saját appunk volt.

Hogyan támadja meg önmagát egy jegyzetelő

A GeekBye fel tud tölteni felvételeket a Google Drive-odba. Ha egy felvételt nem lehet azonnal feltölteni — offline voltál, az app bezárt, a Drive megakadt —, akkor egy backlogba kerül, hogy később újrapróbálkozzon. Ésszerű.

A hiba abban volt, hogy mikor következett be az a „később". A következő indításkor az app megpróbálta az egész backlogot egyszerre kiüríteni. Egy felhasználó egy hétnyi függőben lévő felvétellel egyszerű feltöltési kérések záporrá vált abban a pillanatban, amikor megnyitotta az appot. Szorozd meg minden felhasználóval, aki reggel elindítja, és a backendünktől azt kérték, hogy hitelesítsen, rate-ellenőrizzen és feldolgozzon egy fal forgalmat ugyanabban a néhány másodpercben — olyan kliensektől, amelyek technikailag mind rendesen viselkedtek.

A backendünk azt tette, amit kellett, és rate-limitelte az áradatot. És itt kezdődött a másodrendű kár. A rate-limit válasz egy HTTP 429, és a 429 nagyon hasonlít más hibákra, ha nem vagy óvatos:

  • Az indításkori profile fetch kapott egy 429-et, és az app sikertelen authként kezelte — rövid időre kijelentkeztetve a felhasználókat egy tökéletesen érvényes munkamenetből.
  • A transzkripciós kapcsolat kapott egy általános 429-et, és egy audio-limit elérve upgrade felszólítást mutatott — azt mondva a fizető felhasználóknak, hogy elértek egy kvótát, amit nem értek el.

Így egyetlen gyökérok — egy ütemezetlen backlog — három látható tünetet produkált: szerverterhelést, hamis kijelentkezéseket és hamis upselleket. A self-DoS klasszikus alakja: a terhelés rossz, de a terhelés tüneteinek félreértelmezése az, amit a felhasználók valójában megéreznek.

A javítás, két rétegben

Állítsd meg a tolongást. A feltöltési backlog most ütemezett — a kérések backoffal időben szét vannak húzva, plusz egy cooldown minden 429 után, hogy a kliens hátrébb lépjen egy megterhelt szervertől, ahelyett hogy még jobban ránehezedne. Egy thundering herdből rendezett sor lesz.

Állítsd meg a félreértelmezést. Egy átmeneti 429 vagy 5xx indítás közben már nem jelentkeztet ki — a kliens megkülönbözteti azt, hogy „a szerver rövid ideig elfoglalt", attól, hogy „a munkameneted érvénytelen". És egy általános rate-limit 429 a transzkripciós útvonalon már nem álcázza magát audiokvóta-hibának; csak egy valódi kvóta-válasz mutatja az upgrade felszólítást. A lecke, ami megmaradt: egy hibakód nem egy hibajelentés. A 429 azt jelenti, hogy „lassíts", nem azt, hogy „jogosulatlan vagy", és nem azt, hogy „kifogytál a kvótából" — és a kliensnek tudnia kell a különbséget.

A liveness létra, ami ezt követte

A csorda ütemezése egy csendesebb problémát hozott felszínre: amikor egy kapcsolat tényleg elromlott terhelés alatt, milyen gyorsan vettük észre? Lassabban, mint szerettük volna. Így a v2.0.4 kiépített egy connection-liveness létrát a valós idejű átirathoz:

  • Heartbeatek — a transzkripciós socketet fix időközönként pingeljük, hogy egy csendben halott kapcsolat másodpercek alatt kiderüljön, ahelyett hogy megvárnánk, amíg a következő audiódarab elbukik.
  • Egy újracsatlakozási kick a hálózat visszatértekor — amikor az operációs rendszer jelenti, hogy a hálózat visszatért, az app proaktívan újraépíti a kapcsolatot, ahelyett hogy megvárná, amíg belebotlik a felfedezésbe.
  • Liveness control ping/ponggal — egy alkalmazásszintű oda-vissza út, amely nemcsak azt erősíti meg, hogy „a socket nyitva van", hanem hogy „a másik vég tényleg válaszol".
  • Egy szolgáltatás által birtokolt újracsatlakozási kapu — egyetlen hely dönti el, hogy újracsatlakozzon-e, ahelyett hogy az app több része versengene érte és egymásba gázolnának.

Ebből semmi sem látványos. Mind ez az oka annak, hogy egy v2 munkamenet olyan érzés, mintha egyszerűen... csak kapcsolatban maradna.

Megtörtént megint ezen a héten — egy szinttel lejjebb

Íme a rész, amitől ez több egy háborús történetnél. Ugyanaz a hibaosztály néhány napja újra felütötte a fejét, egy szinttel alattunk. Egyetlen kliens 21 transzkripciós munkamenet-indítást tüzelt el 400 milliszekundum alatt — és átbillentette a speech szolgáltatónk fiókszintű, 15 konkurens kérésre szóló limitjét. Egy tolongás a közös infrastruktúra ellen, pontosan úgy, mint a feltöltési backlog, csak egy függőségre irányítva a saját backendünk helyett.

Az alak azonos, és a javítás is: a tolongó kliensnek egy single-flight guardra van szüksége, hogy ne tudjon húsz indítást eltüzelni egyetlen szándékra, a közös erőforrásnak pedig egy felhasználónkénti plafonra, hogy egy kliens ne tudja felzabálni a poolt. Ennek a kliensoldali változatát már megépítettük korábban — a feltöltési pacer ez a minta. Most a munkamenet-indításokra alkalmazzuk. A thundering herd nem egy bug, amit egyszer kijavítasz; egy alak, amelyet megtanulsz felismerni mindenütt, ahol a kliensek egy közös limitbe ütköznek.

Három dolog, amit érdemes elvinni

  1. A saját klienseid egy terheléses teszt, amelyet nem ütemeztél be. Bármi, ami indításkor kötegel, megnyitáskor újrapróbálkozik vagy ébredéskor újracsatlakozik, egy thundering herd, amely elég felhasználóra vár. Ütemezd, mielőtt megszerzed őket.
  2. Különböztesd meg a kódot a jelentéstől. A 429 a létező legfélreértelmezettebb státusz. A „lassíts" nem „jelentkezz ki", és nem „fizess nekünk". Irányítsd minden hibát afelé, amit valójában jelent.
  3. A liveness egy létra, nem egy flag. Az „él-e a kapcsolat?"-nak több őszinte válasza is van különböző rétegeken — socket nyitva, bájtok áramlanak, a másik vég válaszol, hálózat jelen. Egy robusztus app egynél többet ellenőriz.

Ez a nem látványos gépezet a GeekBye v2 nyugalma alatt. A megbízhatósági funkciókhoz, amelyeket lehetővé tett, lásd miért áll le az AI jegyzetelőd rossz Wi-Fi-n és élő átirat, amikor a tűzfal blokkolja a WebSocketeket (v2.0.8). A sorozat szomszédos kiadásaihoz, miért állítja le az AI-jegyzetelőd a felvételt a meeting kellős közepén (v2.0.9).