Steven
Steven6 min de lectura

El dia que la nostra app es va DDoSejar a si mateixa

Un endarreriment de pujades pendents, alliberat tot de cop en arrencar, va convertir cada client de GeekBye en un petit atac de denegació de servei contra els nostres propis servidors. La solució — i l'escala de vitalitat de connexió que ens va obligar a construir — és una de les coses més útils que ens va ensenyar la v2.

Fiabilitat
Xarxes
Enginyeria
Llançaments de GeekBye
El dia que la nostra app es va DDoSejar a si mateixa

Tot enginyer de sistemes distribuïts acaba trobant-se amb el thundering herd: una massa de clients fent tots el mateix en el mateix instant, i el recurs compartit que hi ha al darrere que cedeix. Normalment són els clients d'algú altre. A GeekBye v2.0.1 eren els nostres, i l'atacant era la nostra pròpia app.

Com un notetaker s'ataca a si mateix

GeekBye pot pujar gravacions al teu Google Drive. Si una gravació no es pot pujar immediatament — estaves fora de línia, l'app es va tancar, Drive va tenir un entrebanc — passa a un endarreriment per reintentar-ho més tard. Assenyat.

La fallada era en quan passava aquest "més tard". A la següent arrencada, l'app intentava buidar tot l'endarreriment de cop. Un usuari amb una setmana de gravacions pendents es convertia en una ràfega de peticions de pujada simultànies l'instant que obria l'app. Multiplica-ho per cada usuari arrencant al matí, i al nostre backend se li demanava autenticar, comprovar la taxa i processar un mur de trànsit en els mateixos pocs segons — de clients que, tècnicament, es comportaven tots correctament.

El nostre backend va fer el correcte i va limitar la taxa de la riuada. I aquí és on va començar el dany de segon ordre. Una resposta de límit de taxa és un HTTP 429, i un 429 s'assembla molt a altres fallades si no vas amb compte:

  • La recuperació del perfil en arrencar va rebre un 429 i l'app la va tractar com auth fallida — tancant breument la sessió dels usuaris d'una sessió perfectament vàlida.
  • La connexió de transcripció va rebre un 429 genèric i va mostrar un avís d'actualització de límit d'àudio assolit — dient a usuaris de pagament que havien arribat a una quota que no havien assolit.

Així que una sola causa arrel — un endarreriment sense ritme — va produir tres símptomes visibles: tensió al servidor, tancaments de sessió falsos i vendes addicionals falses. La forma clàssica d'un self-DoS: la càrrega és dolenta, però és la mala interpretació dels símptomes de la càrrega el que els usuaris realment senten.

La solució, en dues capes

Atura l'estampida. L'endarreriment de pujades ara està ritmat — les peticions es reparteixen amb backoff, i un període de refredament després de qualsevol 429 perquè el client s'allunyi d'un servidor tensat en lloc de recolzar-s'hi més fort. Un thundering herd es converteix en una cua ordenada.

Atura la mala interpretació. Un 429 o 5xx transitori durant l'arrencada ja no et tanca la sessió — el client distingeix "el servidor està breument ocupat" de "la teva sessió no és vàlida". I un 429 genèric de límit de taxa a la via de transcripció ja no es fa passar per un error de quota d'àudio; només una resposta genuïna de quota mostra l'avís d'actualització. La lliçó que va quedar: un codi d'error no és un significat d'error. 429 vol dir "afluixa", no "no estàs autoritzat" i no "t'has quedat sense quota" — i el client ha de saber la diferència.

L'escala de vitalitat que va seguir

Ritmar la manada va destapar un problema més silenciós: quan una connexió que anava malament sota càrrega, amb quina rapidesa ens n'adonàvem? Més lentament del que volíem. Així que la v2.0.4 va desenvolupar una escala de vitalitat de connexió per a la transcripció en temps real:

  • Heartbeats — el socket de transcripció rep un ping a un interval fix, perquè una connexió morta en silenci es detecti en segons en lloc d'esperar que falli el següent tros d'àudio.
  • Una empenta de reconnexió quan torna la xarxa — quan el sistema operatiu informa que la xarxa ha tornat, l'app restableix la connexió de manera proactiva en lloc d'esperar a topar-se amb el descobriment.
  • Vitalitat de control ping/pong — un anada-i-tornada a nivell d'aplicació que confirma no només "el socket és obert" sinó "l'altre extrem realment respon".
  • Una porta de reconnexió propietat del servei — un sol lloc decideix si reconnectar, en lloc de diverses parts de l'app competint per fer-ho i trepitjant-se les unes a les altres.

Res d'això és glamurós. Tot plegat és per què una sessió de v2 se sent com si simplement... es mantingués connectada.

Va tornar a passar aquesta setmana — un nivell més avall

Aquí ve la part que fa que això sigui més que una batalleta. Aquesta mateixa classe de fallada va ressorgir fa uns dies, un nivell per sota de nosaltres. Un sol client va disparar 21 inicis de sessió de transcripció en menys de 400 mil·lisegons — i va superar el límit de tot el compte del nostre proveïdor de veu de 15 peticions concurrents. Una estampida contra infraestructura compartida, exactament com l'endarreriment de pujades, només que apuntant a una dependència en lloc del nostre propi backend.

La forma és idèntica, i també ho és la solució: el client que provoca l'estampida necessita un single-flight guard perquè no pugui disparar vint inicis per una sola intenció, i el recurs compartit necessita un límit per usuari perquè un sol client no pugui consumir tot el pool. Ja hem construït la versió del costat client d'això abans — el ritmador de pujades és aquest patró. Ara l'apliquem als inicis de sessió. El thundering herd no és un bug que arregles una vegada; és una forma que aprens a reconèixer arreu on els clients es troben amb un límit compartit.

Tres coses per emportar-se

  1. Els teus propis clients són una prova de càrrega que no vas programar. Qualsevol cosa que agrupa-en-arrencar, reintenta-en-obrir o reconnecta-en-despertar és un thundering herd esperant prou usuaris. Ritma-ho abans de tenir-los.
  2. Distingeix el codi del significat. 429 és l'estat més mal llegit que existeix. "Afluixa" no és "tanca la sessió" i no és "paga'ns". Encamina cada fallada cap al que realment vol dir.
  3. La vitalitat és una escala, no una bandera. "Està viva la connexió?" té diverses respostes honestes a capes diferents — socket obert, bytes fluint, l'altre extrem responent, xarxa present. Una app robusta en comprova més d'una.

Aquesta és la maquinària poc glamurosa sota la calma de GeekBye v2. Per a les funcions de fiabilitat que va habilitar, mira per què el teu assistent de notes amb IA s'atura amb mal Wi-Fi i transcripció en directe quan el tallafocs bloqueja els WebSockets (v2.0.8). Per als llançaments veïns d'aquesta sèrie, per què el teu assistent de notes amb IA deixa de gravar a mitja reunió (v2.0.9).