Steven
Steven5 min lesing

Dagen appen vår DDoS-et seg selv

En kø av ventende opplastinger, sluppet løs alt på én gang ved oppstart, forvandlet hver GeekBye-klient til et lite tjenestenektangrep (DoS) mot våre egne servere. Fiksen — og den forbindelseslivlighetsstigen den tvang oss til å bygge — er en av de mest nyttige tingene v2 lærte oss.

Pålitelighet
Nettverk
Utvikling
GeekBye-lanseringer
Dagen appen vår DDoS-et seg selv

Enhver ingeniør innen distribuerte systemer møter før eller siden thundering herd — den stormende flokken: en masse klienter som alle gjør det samme i samme øyeblikk, og den delte ressursen bak dem som gir etter. Vanligvis er det noen andres klienter. I GeekBye v2.0.1 var det våre, og angriperen var vår egen app.

Hvordan et notatverktøy angriper seg selv

GeekBye kan laste opp opptak til din Google Drive. Hvis et opptak ikke kan lastes opp med en gang — du var frakoblet, appen lukket seg, Drive hakket — havner det i en kø for å prøve igjen senere. Fornuftig.

Feilen lå i når "senere" inntraff. Ved neste oppstart forsøkte appen å tømme hele køen på én gang. Én bruker med en ukes ventende opptak ble til et sprut av samtidige opplastingsforespørsler i det øyeblikket de åpnet appen. Gang det med hver bruker som starter opp om morgenen, og vår backend ble bedt om å autentisere, hastighetssjekke og behandle en vegg av trafikk innen de samme få sekundene — fra klienter som alle, teknisk sett, oppførte seg.

Vår backend gjorde det rette og hastighetsbegrenset flommen. Og der begynte andre-ordens-skaden. Et hastighetsbegrensningssvar er en HTTP 429, og 429 ligner andre feil til forveksling hvis du ikke er forsiktig:

  • Oppstartens profilhenting fikk en 429, og appen behandlet det som autentisering mislyktes — og logget kortvarig ut brukere fra en fullstendig gyldig økt.
  • Transkripsjonsforbindelsen fikk en generisk 429 og viste en oppgraderingsmelding om nådd lydgrense — og fortalte betalende brukere at de hadde nådd en kvote de ikke hadde nådd.

Så én grunnårsak — en utaktet kø — produserte tre synlige symptomer: serverbelastning, falske utlogginger og falske mersalg. Den klassiske formen på en selv-DoS: belastningen er ille, men det er feiltolkningen av belastningens symptomer brukerne faktisk merker.

Fiksen, i to lag

Stopp stormløpet. Opplastingskøen er nå taktet — forespørsler spres ut med backoff, og en nedkjølingsperiode etter enhver 429, slik at klienten trekker seg unna en belastet server i stedet for å lene seg hardere på den. En thundering herd blir til en ordentlig kø.

Stopp feiltolkningen. En forbigående 429 eller 5xx under oppstart logger deg ikke lenger ut — klienten skiller "serveren er kortvarig opptatt" fra "økten din er ugyldig". Og en generisk hastighetsbegrensnings-429 på transkripsjonsveien maskerer seg ikke lenger som en lydkvotefeil; kun et ekte kvotesvar viser oppgraderingsmeldingen. Lærdommen som satte seg: en feilkode er ikke en feilbetydning. 429 betyr "sakk farten", ikke "du er logget ut" og ikke "kvoten din er brukt opp" — og klienten må vite forskjellen.

Livlighetsstigen som fulgte

Å takte flokken avdekket et mer stille problem: når en forbindelse faktisk gikk galt under belastning, hvor raskt oppdaget vi det? Tregere enn vi ønsket. Så v2.0.4 bygde ut en forbindelseslivlighetsstige for sanntidstranskripsjon:

  • Hjerteslag — transkripsjonssocketen pinges med et fast intervall, slik at en stille død forbindelse oppdages på sekunder i stedet for å vente på at neste lydbit skal feile.
  • Et nettverk-oppe-gjenoppkoblingsspark — når OS-et rapporterer at nettverket er tilbake, gjenoppretter appen forbindelsen proaktivt i stedet for å vente på å snuble inn i oppdagelsen.
  • Kontroll-ping/pong-livlighet — en tur-retur på applikasjonsnivå som bekrefter ikke bare "socketen er åpen", men "den andre enden svarer faktisk".
  • En tjeneste-eid gjenoppkoblingsport — ett sted avgjør om det skal kobles til på nytt, i stedet for at flere deler av appen kappes om å gjøre det og tråkker på hverandre.

Ingenting av dette er glamorøst. Alt av det er grunnen til at en v2-økt føles som om den bare... forblir tilkoblet.

Det skjedde igjen denne uken — ett nivå ned

Her er delen som gjør dette til mer enn en krigshistorie. Samme feilklasse dukket opp igjen for noen dager siden, ett nivå under oss. En enkelt klient avfyrte 21 starter av transkripsjonsøkter på under 400 millisekunder — og utløste taleleverandøren vår sin kontodekkende grense på 15 samtidige forespørsler. Et stormløp mot delt infrastruktur, akkurat som opplastingskøen, bare rettet mot en avhengighet i stedet for vår egen backend.

Formen er identisk, og det er fiksen også: den stormende klienten trenger en single-flight-vakt slik at den ikke kan avfyre tjue starter for én hensikt, og den delte ressursen trenger et tak per bruker slik at én klient ikke kan bruke opp hele bassenget. Vi har bygd klientsideversjonen av dette før — opplastingstakteren er dette mønsteret. Nå anvender vi det på øktstarter. Thundering herd er ikke en feil du fikser én gang; det er en form du lærer å gjenkjenne overalt hvor klienter møter en delt grense.

Tre ting å ta med seg

  1. Dine egne klienter er en belastningstest du ikke planla. Alt som bunter-ved-oppstart, prøver-igjen-ved-start eller kobler-til-igjen-ved-oppvåkning er en thundering herd som venter på nok brukere. Takt det før du har dem.
  2. Skill koden fra betydningen. 429 er den mest feillest statusen som finnes. "Sakk farten" er ikke "logg ut" og ikke "betal oss". Dirigér hver feil dit den faktisk betyr.
  3. Livlighet er en stige, ikke et flagg. "Er forbindelsen i live?" har flere ærlige svar på ulike lag — socket åpen, bytes flyter, den andre enden svarer, nettverk til stede. En robust app sjekker mer enn ett.

Dette er det uglamorøse maskineriet under roen i GeekBye v2. For pålitelighetsfunksjonene den muliggjorde, se hvorfor AI-notatverktøyet ditt stopper på dårlig Wi-Fi og livetranskripsjon når brannmuren blokkerer WebSockets (v2.0.8). For nabolanseringene i denne serien, hvorfor AI-notattakeren din slutter å ta opp midt i møtet (v2.0.9).