Jährlich Jährlich zahlen und bei jedem bezahlten Tarif 2 Monate gratis erhalten. Tarife ansehen
Anmelden Kostenlos starten

Die Nutzungsseite zeigte zu viel an, und kein Geld bewegte sich

Beginnen wir mit dem Teil, der beim Lesen des Rests am leichtesten verloren geht: Es wurde kein Geld genommen. Keine Guthabenkarte wurde doppelt belastet, keine Karte wurde abgerechnet, kein Abo-Kontingent wurde für Arbe…

Beginnen wir mit dem Teil, der beim Lesen des Rests am leichtesten verloren geht: Es wurde kein Geld genommen. Keine Guthabenkarte wurde doppelt belastet, keine Karte wurde abgerechnet, kein Abo-Kontingent wurde für Arbeit verbraucht, die nie stattfand.

Kaputt war eine Zahl auf einem Bildschirm. Bei einigen Konten zeigte die Nutzungsaufschlüsselung auf der eigenen Kontoseite zu viel an — beim schlechtesten von uns gemessenen Konto das 2,4-Fache des Hauptbuchs, das sie zusammenfassen soll. Das Hauptbuch war die ganze Zeit korrekt. Ebenso die monatlichen Obergrenzen, die von ganz woanders gelesen werden. Es war ein Anzeigefehler, er ist behoben, und hier ist seine Ursache.

Ein Schlüssel, der nach etwas suchte, das niemand geschrieben hat

Arbeit, die durch eine Ihrer Aktionen ausgelöst wird — eine tägliche Nutzungszusammenfassung aktualisieren, eine Empfehlungsgutschrift buchen, eine Benachrichtigung verteilen — passiert nicht innerhalb Ihrer Anfrage. Die Anfrage schreibt eine Nachricht, und ein Hintergrund-Consumer nimmt sie einen Moment später auf. Genau das verhindert, dass ein langsamer Nebeneffekt eine schnelle Seite langsam macht.

Die Gefahr dieser Anordnung ist, dieselbe Arbeit zweimal zu tun, denn ein Broker liefert dieselbe Nachricht nach einer Störung bereitwillig erneut aus. Die Verteidigung ist ein Posteingang: Vor der Verarbeitung beansprucht ein Consumer die Nachricht, indem er eine Zeile mit deren Schlüssel schreibt, und ein eindeutiger Index macht einen zweiten Anspruch unmöglich.

Unserer beanspruchte über ein Paar — den Mandanten, zu dem die Nachricht gehörte, plus den Nachrichtenschlüssel — und schlug dieses Paar gegen die Mandanten-ID nach, die der Nachrichtenumschlag trägt und die gefüllt ist.

Die Anspruchszeilen trugen überhaupt keine Mandanten-ID. Jede einzelne war leer, und das von Anfang an. Die Dokumentation der Entität behauptete wörtlich, das Framework stemple sie beim Einfügen. Das tat es nie. Am Tag des Fundes in der Produktion gemessen: alle 3.344 Posteingangszeilen leer, während auf der erzeugenden Seite 1.174 von 1.451 Zeilen einen echten Mandanten trugen — genau deshalb verdächtigte niemand die lesende Seite.

Die Suche fragte also nach (Mandant, Schlüssel), die gespeicherte Zeile war (nichts, Schlüssel), und sie konnte niemals übereinstimmen.

Nichts schlug fehl, und genau darum lief es wochenlang

Eine Suche, die niemals übereinstimmen kann, klingt, als müsste sie beim ersten Versuch einen Fehler werfen. Sie tat das Gegenteil.

Der Anspruch entschied, die Nachricht sei neu. Das Einfügen lief dann in den eindeutigen Index und wurde als Duplikat abgelehnt — korrekt, denn die Zeile war bereits da. Ein Ausweichzweig fing diese Ablehnung ab und las sie wohlwollend: Eine andere Zustellung muss diese beansprucht haben, hier gibt es nichts zu tun. Der Umschlag blieb im Zustand „noch zu verarbeiten", und weil er noch zu verarbeiten war, griff ihn die nächste Zustellung auf und erledigte die Arbeit erneut.

Jeder Consumer verarbeitete seinen eigenen Rückstand etwa alle fünfundvierzig Sekunden erneut, endlos. Sechsundzwanzig taten das. Eine Produktionszeile wurde 333 Stunden nach ihrer Ankunft fertig — und war den Großteil dieser Zeit immer wieder fertig geworden.

Was tatsächlich kaputt war

Von diesen sechsundzwanzig Consumern akkumulieren vier, statt zu überschreiben. Diese vier sind die Abrechnungsprojektionen, aus denen die Nutzungsseite liest, und dort trat es zutage.

Sie hatten einen ausdrücklichen Schutz genau dagegen, geschrieben nach einem Vorfall im August, bei dem ein einzelnes Ereignis neunmal auf eine Anzeige angewendet wurde. Der Schutz markiert eine Zeile vor der Verarbeitung als erfolgreich, damit eine erneute Zustellung die Markierung findet und stoppt. Die Markierung wurde jedes Mal korrekt geschrieben. Der nächste Anspruch suchte sie dann mit demselben kaputten Schlüssel und fand sie nicht. Die Garantie war seit dem Tag ihrer Auslieferung leer.

Was dies nicht berührt hat, lohnt eine genaue Aufzählung, denn „Abrechnungsprojektion" klingt alarmierend, und die Fakten sind es nicht:

  • Das Hauptbuch war korrekt. Jeder KI-Aufruf schreibt genau einen Abrechnungssatz mit seinem eigenen Preis-Snapshot, und nichts auf diesem Pfad schreibt diese Sätze.
  • Kein Guthaben bewegte sich. Salden kommen aus dem Hauptbuch, nie aus einer Zusammenfassung.
  • Keine Obergrenze bewegte sich. Der Budgetwächter liest einen separaten Zähler, und der ist die einzige Wahrheitsquelle dafür, was Sie diesen Monat ausgegeben haben. Niemand wurde wegen einer aufgeblähten Zahl zu früh gestoppt, und niemand wurde durchgelassen, der es nicht hätte sein sollen.

Das Einzige, was die betroffene Zusammenfassung liest, ist die Nutzungshistorie auf Ihrer eigenen Kontoseite — Ihre Summe und die Aufschlüsselung, wofür sie anfiel. Das ist der gesamte Wirkungsradius, und was er tat, war zu hoch anzuzeigen.

Die Hälfte, auf die niemand schaute

Dieselbe kaputte Suche saß an einer zweiten Stelle: dem Pfad, der läuft, wenn eine Nachricht fehlschlägt. Dort fand sie die zu aktualisierende Zeile nie, und direkt darunter saß ein Zweig mit dem Kommentar „in der Praxis unerreichbar" — der bei jedem Fehlschlag „dieser Fehler ist endgültig" zurückgab.

So kam der Wiederholungszähler nie voran, und keine vergiftete Nachricht erreichte je die Dead-Letter-Queue. Eine ganze Schicht Fehlerbehandlung war abgeschaltet, hinter einem Kommentar, der jedem Leser sagte, hier gebe es nichts zu prüfen.

Die Behebung

Beide Suchen arbeiten nun über den Nachrichtenschlüssel innerhalb der Consumer-Gruppe, was der eindeutige Index ohnehin erzwingt. Nachrichtenschlüssel leiten sich aus der ID des erzeugenden Objekts ab, können also über Mandanten hinweg konstruktionsbedingt nicht kollidieren.

Die Dokumentation, die das Stempeln behauptete, wurde korrigiert statt gelöscht. Dieser Satz war die Falle, und ihn zu löschen hätte nichts hinterlassen, das sagt, dass endlich jemand nachgesehen hat.

Neun Tests halten das Verhalten fest. Sechs davon schlagen gegen den alten Code fehl, was wir bestätigt haben, indem wir den alten Code zurückgesetzt haben.

Aufräumen danach

Die aufgeblähten Zeilen reparieren sich selbst. Ein Worker baut die Tageszusammenfassungen stündlich über ein Zwei-Tage-Fenster aus dem Hauptbuch neu auf. Er lief die ganze Zeit und verlor: einmal pro Stunde gegen einen Consumer, der alle fünfundvierzig Sekunden erneut anwendet, ist kein fairer Wettlauf.

Da der Consumer nicht mehr erneut läuft, gewinnt er. Die zwei Tage, die noch in diesem Fenster lagen, wurden aus dem Hauptbuch neu aufgebaut, sobald die Behebung live war. Zwei frühere Tage waren bereits herausgefallen, und was auf ihnen bleibt, ist ein Konto von uns selbst plus eine vernachlässigbare Differenz anderswo — also ließen wir sie stehen, statt ein Produktionsfenster zu verbreitern, um dem nachzujagen. Das schien besser, als zu suggerieren, der Durchlauf sei vollständig gewesen.

Was wir daraus mitgenommen haben

Ein Deduplizierungsschlüssel muss spiegeln, was der Index tatsächlich erzwingt, nicht das, was der Code glaubt, geschrieben zu haben. Die beiden Hälften dieses Schlüssels wurden aus verschiedenen Feldern gespeist, und keine war je gegen die andere gemessen worden. Beide sind es jetzt, und der Test, der das beweist, wird rot gegen die Version, die falsch war.

Teilen LinkedIn X E-Mail
Probieren Sie es mit Ihrer eigenen Arbeit ausJede Antwort zeigt, was sie gekostet hat. Starten Sie mit dem kostenlosen Plan. Kostenlos starten
Produkt-Updates statt Rauschen Neue Modelle, Funktionen und Changelog-Highlights – eine kurze E-Mail, wenn wirklich etwas live geht.
Mit dem Abonnieren stimmen Sie zu, E-Mails mit Produkt-Updates von ToRun zu erhalten. Sie können sich jederzeit abmelden. Datenschutzerklärung