
Der Tag, an dem unsere App sich selbst DDoSte
Ein Backlog ausstehender Uploads, beim Start alle auf einmal losgelassen, verwandelte jeden GeekBye-Client in einen kleinen Denial-of-Service-Angriff auf unsere eigenen Server. Der Fix — und die Verbindungs-Vitalitätsleiter, die er uns zu bauen zwang — ist eines der nützlichsten Dinge, die uns v2 gelehrt hat.
Jeder Ingenieur verteilter Systeme trifft irgendwann auf den Ansturm (die thundering herd): eine Masse von Clients, die alle im selben Moment dasselbe tun, und die gemeinsame Ressource dahinter, die einknickt. Normalerweise sind es die Clients eines anderen. In GeekBye v2.0.1 waren es unsere, und der Angreifer war unsere eigene App.
Wie ein Notetaker sich selbst angreift
GeekBye kann Aufnahmen in dein Google Drive hochladen. Wenn eine Aufnahme nicht sofort hochgeladen werden kann — du warst offline, die App wurde geschlossen, Drive hatte einen Schluckauf — landet sie in einem Backlog, um es später erneut zu versuchen. Sinnvoll.
Der Fehler lag im Wann dieses „später". Beim nächsten Start versuchte die App, den gesamten Backlog auf einmal zu leeren. Ein Nutzer mit einer Woche ausstehender Aufnahmen wurde in dem Augenblick, in dem er die App öffnete, zu einem Schwall gleichzeitiger Upload-Anfragen. Multipliziere das mit jedem Nutzer, der morgens startet, und unser Backend sollte in denselben wenigen Sekunden authentifizieren, Limits prüfen und eine Mauer aus Traffic verarbeiten — von Clients, die sich technisch gesehen alle korrekt verhielten.
Unser Backend tat das Richtige und drosselte die Flut. Und da begann der Schaden zweiter Ordnung. Eine Rate-Limit-Antwort ist ein HTTP 429, und ein 429 sieht anderen Fehlern sehr ähnlich, wenn man nicht aufpasst:
- Der Profil-Abruf beim Start bekam ein
429, und die App behandelte es als Authentifizierung fehlgeschlagen — was Nutzer kurz aus einer völlig gültigen Sitzung ausloggte. - Die Transkriptionsverbindung bekam ein generisches
429und zeigte eine Upgrade-Aufforderung wegen erreichtem Audio-Limit — und teilte zahlenden Nutzern mit, sie hätten eine Quota erreicht, die sie gar nicht angerührt hatten.
So erzeugte eine einzige Grundursache — ein ungetakteter Backlog — drei sichtbare Symptome: Serverlast, falsche Logouts und falsche Upsells. Die klassische Form eines Self-DoS: Die Last ist schlecht, aber was Nutzer wirklich spüren, ist die Fehldeutung der Symptome dieser Last.
Der Fix, in zwei Schichten
Den Ansturm stoppen. Der Upload-Backlog wird jetzt getaktet — Anfragen verteilen sich mit Backoff, und nach jedem 429 gibt es eine Abkühlphase, damit der Client sich von einem belasteten Server zurückzieht, statt stärker gegen ihn zu drücken. Ein Ansturm wird zu einer geordneten Warteschlange.
Die Fehldeutung stoppen. Ein transientes 429 oder 5xx beim Start loggt dich nicht mehr aus — der Client unterscheidet „der Server ist kurz beschäftigt" von „deine Sitzung ist ungültig". Und ein generisches Rate-Limit-429 auf dem Transkriptionspfad tarnt sich nicht mehr als Audio-Quota-Fehler; nur eine echte Quota-Antwort zeigt die Upgrade-Aufforderung. Die Lektion, die haften blieb: Ein Fehlercode ist keine Fehlerbedeutung. 429 heißt „langsamer", nicht „du bist nicht autorisiert" und nicht „deine Quota ist aufgebraucht" — und der Client muss den Unterschied kennen.
Die Vitalitätsleiter, die folgte
Den Ansturm zu takten legte ein leiseres Problem offen: Wenn eine Verbindung unter Last tatsächlich schlecht wurde, wie schnell bemerkten wir es? Langsamer, als uns lieb war. Also baute v2.0.4 eine Verbindungs-Vitalitätsleiter für die Echtzeit-Transkription:
- Heartbeats — der Transkriptions-Socket wird in festen Intervallen gepingt, damit eine still gestorbene Verbindung in Sekunden erkannt wird, statt zu warten, bis der nächste Audio-Chunk scheitert.
- Ein Reconnect-Anstoß bei zurückkehrendem Netz — wenn das Betriebssystem meldet, dass das Netz wieder da ist, baut die App die Verbindung proaktiv neu auf, statt zu warten, bis sie über die Entdeckung stolpert.
- Vitalität per Control-Ping/Pong — ein Roundtrip auf Anwendungsebene, der nicht nur bestätigt, dass „der Socket offen ist", sondern dass „die Gegenstelle tatsächlich antwortet".
- Ein vom Service verwaltetes Reconnect-Gate — eine einzige Stelle entscheidet, ob neu verbunden wird, statt dass mehrere Teile der App darum wettrennen und sich gegenseitig in die Quere kommen.
Nichts davon ist glamourös. All das ist der Grund, warum sich eine v2-Sitzung anfühlt, als bliebe sie einfach... verbunden.
Es passierte diese Woche wieder — eine Stufe tiefer
Hier ist der Teil, der das mehr macht als eine Kriegsgeschichte. Dieselbe Fehlerklasse tauchte vor Tagen wieder auf, eine Ebene unter uns. Ein einzelner Client feuerte 21 Transkriptions-Session-Starts in unter 400 Millisekunden — und riss das kontoweite Limit unseres Sprach-Anbieters von 15 gleichzeitigen Anfragen. Ein Ansturm gegen gemeinsame Infrastruktur, genau wie der Upload-Backlog, nur auf eine Abhängigkeit gerichtet statt auf unser eigenes Backend.
Die Form ist identisch, und der Fix auch: Der stürmende Client braucht einen Single-Flight-Schutz, damit er nicht zwanzig Starts für eine Absicht feuern kann, und die gemeinsame Ressource braucht eine Obergrenze pro Nutzer, damit ein Client nicht den ganzen Pool verbrauchen kann. Die clientseitige Version davon haben wir schon gebaut — der Upload-Taktgeber ist dieses Muster. Jetzt wenden wir es auf Session-Starts an. Der Ansturm ist kein Bug, den man einmal behebt; er ist eine Form, die man überall dort erkennen lernt, wo Clients auf ein gemeinsames Limit treffen.
Drei Dinge zum Mitnehmen
- Deine eigenen Clients sind ein Lasttest, den du nicht geplant hast. Alles, was beim-Start-bündelt, beim-Launch-neu-versucht oder beim-Aufwachen-neu-verbindet, ist ein Ansturm, der nur auf genug Nutzer wartet. Takte ihn, bevor du sie hast.
- Unterscheide den Code von der Bedeutung.
429ist der am meisten fehlgelesene Status, den es gibt. „Langsamer" ist nicht „logg dich aus" und nicht „zahl uns". Leite jeden Fehler dorthin, was er wirklich bedeutet. - Vitalität ist eine Leiter, kein Flag. „Ist die Verbindung am Leben?" hat mehrere ehrliche Antworten auf verschiedenen Schichten — Socket offen, Bytes fließen, Gegenstelle antwortet, Netz vorhanden. Eine robuste App prüft mehr als eine.
Das ist die glanzlose Mechanik unter der Ruhe von GeekBye v2. Für die Zuverlässigkeitsfunktionen, die sie ermöglicht hat, siehe warum dein KI-Notetaker bei schlechtem WLAN stoppt und Live-Transkription, wenn die Firewall WebSockets blockiert (v2.0.8). Für die benachbarten Releases dieser Serie warum dein KI-Notetaker mitten im Meeting die Aufnahme stoppt (v2.0.9).