Race Conditions beheben: flüchtige asynchrone Fehler in Ihrer App stoppen
Lernen Sie, Race Conditions zu beheben: Erkennen Sie nicht-deterministisches Verhalten in Queues, Webanfragen und Zustandsaktualisierungen und stabilisieren Sie es.

Wie flüchtige asynchrone Fehler im echten Leben aussehen
Ein flüchtiger asynchroner Fehler tritt auf, wenn Sie zweimal das Gleiche tun und zwei unterschiedliche Ergebnisse bekommen. Sie klicken denselben Button, senden dasselbe Formular oder führen denselben Job aus — und das Ergebnis ändert sich. Einmal klappt es. Beim nächsten Mal schlägt es fehl oder es funktioniert nur teilweise und hinterlässt Daten in einem seltsamen Zustand.
Asynchrone Abläufe begünstigen das, weil Aufgaben nicht nacheinander, sauber abgeschlossen werden. Anfragen, Queue-Jobs, Timer und Datenbank-Schreibvorgänge können sich überschneiden. Die Reihenfolge kann sich durch kleine Zeitunterschiede ändern: Netzwerklatenz, eine langsame Datenbankabfrage, ein Retry oder ein Benutzer, der dieselbe Aktion zweimal ausführt.
Deshalb wirken Race Conditions oft „zufällig“. Der Fehler ist real, zeigt sich aber nur, wenn zwei Dinge in einer unglücklichen Reihenfolge passieren. Zum Beispiel:
- Ein Nutzer macht einen Doppelklick auf „Jetzt bezahlen“ und zwei Requests erstellen zwei Bestellungen.
- Ein Hintergrundjob wiederholt sich nach einem Timeout, obwohl der erste Versuch eigentlich erfolgreich war.
- Zwei Tabs aktualisieren dasselbe Profil und die zuletzt einlaufende Antwort überschreibt neuere Daten.
- Ein Webhook trifft ein, bevor der Datensatz, von dem er abhängt, committed wurde.
Sie müssen kein Experte sein, um das zu diagnostizieren. Wenn Sie die Frage „Was geschah zuerst?“ beantworten können, denken Sie bereits in der richtigen Richtung. Ziel ist es, mit Beobachtung statt mit Vermutungen zu arbeiten: Welche Aktionen liefen, in welcher Reihenfolge, und was glaubte jede Aktion zu dem Moment über den aktuellen Zustand?
Wo Race Conditions sich meist verbergen
Race Conditions sitzen selten in der offensichtlichen Codezeile, die Sie gerade anstarren. Sie verstecken sich in den Lücken zwischen Schritten: nach einem Klick, bevor ein Retry abgeschlossen ist, während ein Hintergrundjob noch läuft. Wenn Sie Race Conditions beheben wollen, beginnen Sie damit, jeden Ort zu kartieren, an dem Arbeit zweimal oder in anderer Reihenfolge als erwartet geschehen kann.
Ein häufiger Versteckort sind Queues und Hintergrundjobs. Ein Event führt zu zwei Jobs (oder derselbe Job wird erneut zugestellt) und beide laufen für sich genommen fehlerfrei. Zusammengenommen erzeugen sie Duplikate, verarbeiten in falscher Reihenfolge oder lösen eine Retry-Flut aus, die das System zufällig aussehen lässt.
Webanfragen sind eine weitere klassische Quelle. Nutzer doppelklicken, mobile Netzwerke wiederholen Anfragen, und Browser halten parallele Tabs länger offen als erwartet. Zwei Requests treffen denselben Endpoint, lesen denselben alten Zustand und schreiben beide zurück — und der letzte Write gewinnt stillschweigend.
Zustandsaktualisierungen sind der Ort, an dem es subtil wird. Sie haben möglicherweise zwei gültige Updates, die kollidieren. Ein späteres Update kann einen neueren Wert überschreiben, weil es früher gestartet wurde oder weil der Code davon ausgeht, der einzige Schreibende zu sein.
Hier sind die Stellen, die Sie zuerst prüfen sollten:
- Queue-Consumer und Worker, die parallel laufen können
- Cron-Jobs, die sich überlappen, wenn ein Durchlauf langsam ist
- „Retry on failure“-Logik, die nicht idempotent ist
- Drittanbieter-API-Aufrufe, die teilweise erfolgreich sein können, bevor sie timeouts auslösen
- Jeder Ablauf, der Read-Modify-Write ohne Schutz durchführt
Beispiel: Eine KI-generierte App verschickt eine „Willkommensmail“ aus einer Webanfrage und zusätzlich aus einem Hintergrundjob „zur Sicherheit“. Unter Last feuern beide Pfade und Duplikate erscheinen nur manchmal. Der Fehler liegt nicht im Mail-Code, sondern in der fehlenden Regel, wer berechtigt ist zu senden und wie oft.
Schnelle Hinweise, die auf Nicht-Determinismus deuten
Nicht-deterministische Bugs fühlen sich wie Pech an: ein 500-Fehler erscheint, Sie drücken Aktualisieren und es funktioniert. Das Muster „verschwindet beim Retry“ ist eines der deutlichsten Anzeichen dafür, dass es um Timing geht, nicht um eine einzelne defekte Codezeile.
Beobachten Sie die Daten. Wenn ein Datensatz manchmal fehlt, manchmal dupliziert ist oder manchmal von einem älteren Wert überschrieben wird, greift mehrere Stellen etwas Gleiches an. Häufig äußert es sich als „Zahlung wurde doppelt erfasst“, „E-Mail doppelt gesendet“ oder „Profil gespeichert, aber Felder sind zurückgesetzt“.
Die Logs verraten oft die Wahrheit, selbst wenn der Bug nicht zuverlässig auftritt. Wenn Sie dieselbe Aktion zweimal mit demselben User und Payload sehen (zwei Job-Runs, zwei API-Calls, zwei Webhook-Handler), gehen Sie von Concurrency oder Retries aus, bis das Gegenteil bewiesen ist.
Schnelle Signale zum Suchen
Diese Muster tauchen immer wieder auf, wenn Sie Race Conditions beheben müssen:
- Fehlerberichte, die sagen „Ich kann es nicht reproduzieren“, besonders über verschiedene Geräte oder Netzwerke hinweg
- Fehler, die bei höherer Last oder langsamerer Datenbank ansteigen
- Ein Workflow, der nur fehlschlägt, wenn mehrere Tabs offen sind oder Nutzer doppelklicken
- Eine Job-Queue, die „manchmal“ in falscher Reihenfolge verarbeitet oder nach Retries Duplikate erzeugt
- Support-Tickets, die sich um Timeouts, Retries oder degradierte Performance häufen
Ein konkretes Beispiel: Zwei Checkout-Requests kommen kurz hintereinander an (Doppelklick + langsame Antwort). Beide sehen „Inventar verfügbar“, reservieren es und nur einer scheitert später. Der Retry „behebt“ das dann und kaschiert das eigentliche Problem.
Wenn Sie einen KI-generierten Prototyp geerbt haben, sind diese Symptome typisch, weil asynchrone Abläufe oft ohne klare Zuständigkeit zusammengeflickt wurden. FixMyMess auditiert diese Pfade schnell, indem jede Anfrage und jeder Joblauf end-to-end getraced wird, bevor am Code herumgeschraubt wird.
Erst instrumentieren: das minimale Logging, das sich auszahlt
Wenn Sie Race Conditions durch „etwas warten“ oder Sleep-Aufrufe zu beheben versuchen, machen Sie den Fehler meist schwerer sichtbar. Ein paar gut gewählte Logs sagen Ihnen, was wirklich passiert, selbst wenn das Versagen selten ist.
Beginnen Sie damit, jeder Nutzeraktion oder jedem Joblauf eine Correlation-ID zu geben. Verwenden Sie sie überall: in der Webanfrage, im Queue-Job und bei allen Downstream-Calls. Wenn jemand meldet „es ist einmal fehlgeschlagen“, können Sie einen Thread durchs ganze System ziehen, statt in unverwandtem Rauschen zu lesen.
Loggen Sie Start und Ende jedes wichtigen Schrittes mit Zeitstempeln. Halten Sie es langweilig und konsistent, damit Sie Läufe vergleichen können. Protokollieren Sie außerdem die gemeinsame Ressource, die berührt wird, wie Datenbank-Zeilen-ID, Cache-Key, E-Mail-Adresse oder Dateiname. Die meisten flüchtigen asynchronen Fehler passieren, wenn zwei Flows dasselbe Objekt in unterschiedlicher Reihenfolge berühren.
Ein minimales Logging-Set, das sich auszahlt:
- correlation_id, user_id (oder session_id) und request_id/job_id
- Event-Name und Schrittname (start, finish)
- Zeitstempel und Dauer
- Ressourcen-Identifikatoren (Row-ID, Cache-Key, Dateiname)
- retry_count und error_type (Timeout vs Validation vs Conflict)
Machen Sie Logs sicher. Geben Sie niemals Secrets, Tokens, rohe Passwörter oder vollständige Kreditkartendaten aus. Wenn Sie „denselben Wert“ bestätigen müssen, loggen Sie einen kurzen Fingerabdruck (z. B. die letzten 4 Zeichen oder eine maskierte Version).
Beispiel: Ein Nutzer klickt zweimal auf „Bezahlen“. Mit Correlation-IDs und Start/Finish-Logs sehen Sie zwei Requests, die um dieselbe order_id konkurrieren, einer retried nach einem Timeout. An diesem Punkt holen Teams oft FixMyMess ins Boot: Wir auditieren den Code und fügen die richtige Instrumentierung hinzu, bevor wir die Logik anfassen, sodass die wahre Ursache offensichtlich wird.
Eine verlässliche Reproduktion erstellen — ohne zu raten
Flüchtige asynchrone Fehler wirken unmöglich, weil sie verschwinden, wenn man sie beobachtet. Der schnellste Weg, Race Conditions zu beheben, ist aufzuhören mit „probieren“ und eine wiederholbare Fehlersituation zu bauen.
Wählen Sie einen Flow, der fehlschlägt, und schreiben Sie die erwartete Reihenfolge in einfachen Worten. Kurz halten, z. B.: „User klickt Pay -> Request erstellt Bestellung -> Job reserviert Inventar -> UI zeigt Erfolg.“ Das wird Ihre Baseline für das, was passieren sollte und was stattdessen passiert.
Machen Sie den Fehler jetzt leichter triggerbar, indem Sie Timing und Parallelität künstlich erhöhen. Warten Sie nicht, bis es natürlich auftritt.
- Erzwingen Sie parallele Aktionen: Doppelklick, zwei Tabs öffnen oder zwei Worker gegen dieselbe Queue laufen lassen.
- Fügen Sie absichtlich Delay in den verdächtigen Schritt ein (vor dem Schreiben, nach dem Lesen, vor einem externen API-Aufruf).
- Verlangsamen Sie die Umgebung: drosseln Sie das Netzwerk, erhöhen Sie DB-Latenz oder lassen Sie die Aufgabe in einer engen Schleife laufen.
- Reduzieren Sie den Umfang: Reproduzieren Sie im kleinsten isolierbaren Handler oder Job.
- Schreiben Sie die genauen Eingaben und das Timing auf, damit jeder das Szenario wiederholen kann.
Ein konkretes Beispiel: Der Button „Projekt erstellen“ erzeugt manchmal zwei Projekte. Fügen Sie 300 ms Verzögerung kurz vor dem Insert ein, klicken Sie zweimal schnell oder senden Sie von zwei Tabs. Wenn Sie Duplikate in 8 von 10 Fällen provozieren können, haben Sie etwas Verwertbares.
Halten Sie ein kurzes Repro-Skript (auch nur ein paar manuelle Schritte) und behandeln Sie es wie einen Test: Führen Sie es vor und nach jeder Änderung aus. Wenn Sie einen KI-generierten Code erben, bauen Teams wie FixMyMess oft zuerst dieses Repro, weil es eine vage Beschwerde in ein messbares Versagen verwandelt.
Schritt für Schritt: Stabilisieren Sie den Ablauf, statt dem Timing hinterherzujagen
Wenn Sie Race Conditions mit „Delay hinzufügen“ oder „warten bis es fertig ist“ zu lösen versuchen, verlagert sich der Fehler meist. Ziel ist, den Ablauf sicher zu machen, auch wenn Dinge zweimal, in falscher Reihenfolge oder gleichzeitig geschehen.
Nennen Sie zunächst das gemeinsame Objekt, das korrumpiert werden kann. Oft ist es eine einzelne Zeile (User-Record), ein Zähler (Saldo) oder eine limitierte Ressource (Inventar). Wenn zwei Pfade es gleichzeitig berühren können, ist das das eigentliche Problem, nicht das Timing.
Ein praktischer Weg ist, eine Schutzstrategie zu wählen und konsistent anzuwenden:
- Kartieren Sie, wer die gemeinsame Ressource liest und wer schreibt (Request A, Job B, Webhook C).
- Entscheiden Sie die Regel: Arbeit serialisieren (nacheinander), die Ressource sperren oder die Operation idempotent machen.
- Fügen Sie einen Idempotency-Key für Aktionen hinzu, die retried werden könnten (Doppelklick, Netzwerk-Retry, Queue-Redelivery).
- Schützen Sie Schreibvorgänge mit einer Transaktion oder einem bedingten Update, damit Sie nicht die Änderung eines anderen verlieren.
- Beweisen Sie es mit Concurrency-Tests und wiederholten Läufen, nicht mit einmaligem „auf meinem Rechner sieht's gut aus“.
Beispiel: Zwei „Bestellung platzieren“-Requests treffen gleichzeitig ein. Ohne Schutz lesen beide inventory=1, beide subtrahieren und Sie verschicken zwei Artikel. Mit Idempotenz verwendet die zweite Anfrage das Ergebnis der ersten erneut. Mit einem bedingten Update (nur subtrahieren, wenn inventory noch 1) innerhalb einer Transaktion kann nur eine Anfrage erfolgreich sein.
Wenn Sie KI-generierten Code geerbt haben, fehlen diese Schutzmaßnahmen oft oder sind inkonsistent. FixMyMess beginnt typischerweise damit, minimale Idempotenz- und sichere Schreibregeln hinzuzufügen und das Szenario dann dutzende Male durchlaufen zu lassen, bis es langweilig stabil ist.
Queues und Hintergrundjobs: Duplikate, Retries, Reihenfolge
Die meisten Queues liefern Jobs mindestens einmal aus. Das heißt, derselbe Job kann zweimal laufen oder nach einem neueren Job eintreffen, selbst wenn Sie nur einmal geklickt haben. Wenn Ihr Handler annimmt, er sei der einzige Lauf, entstehen seltsame Ergebnisse: doppelte Abbuchungen, doppelte E-Mails oder ein Datensatz, der zwischen zwei Zuständen hin- und herspringt.
Die sicherste Vorgehensweise ist, jeden Job wiederholbar sicher zu machen. Denken Sie in Ergebnissen, nicht in Versuchen. Ein Job sollte erneut laufen können und trotzdem im selben Endzustand landen.
Ein praktisches Muster, das beim Beheben von Race Conditions in der Hintergrundverarbeitung hilft:
- Fügen Sie einen Idempotency-Key hinzu (orderId, userId + action + date) und speichern Sie „bereits verarbeitet“, bevor Sie Side-Effects ausführen.
- Zeichnen Sie einen klaren Job-Status auf (pending, running, done, failed), damit Wiederholungen frühzeitig abbrechen können.
- Behandeln Sie externe Aufrufe (Zahlung, E-Mail, Datei-Upload) als „einmal ausführen“ mit eigenen Dedupe-Checks.
- Schützen Sie gegen Out-of-Order-Events mit einer Versionsnummer oder einem Zeitstempel und ignorieren Sie ältere Updates.
- Trennen Sie retrybare Fehler (Timeouts, Rate-Limits) von nicht-retrybaren (falsche Eingaben) und hören Sie auf zu retryen, wenn es niemals erfolgreich wird.
Reihenfolgenprobleme sind leicht zu übersehen. Beispiel: Ein „Subscription kündigen“-Job läuft, dann kommt ein verzögerter „Subscription erneuern“-Job und aktiviert den Nutzer wieder. Wenn Sie eine monoton ansteigende Version (oder updatedAt) bei jeder Änderung speichern, kann der Handler veraltete Nachrichten ablehnen und die neue Wahrheit behalten.
Seien Sie vorsichtig mit globalen Locks. Sie können den Bug verbergen, indem alles langsamer wird, aber in Produktion schaden sie, weil sie nicht zusammenhängende Arbeit blockieren. Bevorzugen Sie per-Entity-Locking (ein Nutzer oder eine Bestellung zur Zeit) oder Idempotency-Checks.
Wenn Sie einen KI-generierten Queue-Worker geerbt haben, der zufällig Arbeit dupliziert, fangen Teams wie FixMyMess oft damit an, Idempotenz und „stale event“-Prüfungen hinzuzufügen. Diese beiden Änderungen machen flüchtiges Verhalten meist schnell vorhersagbar.
Webanfragen: Doppelklicks, Retries und parallele Sessions
Die meisten flüchtigen Request-Bugs entstehen, wenn dieselbe Aktion zweimal gesendet wird oder wenn zwei Aktionen in falscher Reihenfolge ankommen. Nutzer doppelklicken, Browser wiederholen bei schlechtem Netzwerk, Mobile-Apps senden nach einem Timeout erneut, und mehrere Tabs verhalten sich wie separate Nutzer.
Um Race Conditions hier zu beheben, gehen Sie davon aus, dass der Client Duplikate sendet. Der Server muss korrekt sein, auch wenn die UI falsch, langsam oder kurz offline ist.
Machen Sie Endpoints, die etwas ausführen, wiederholbar sicher
Wenn eine Anfrage Side-Effects erzeugen kann (Karte belasten, Bestellung erstellen, E-Mail senden), machen Sie sie idempotent. Ein einfacher Ansatz ist ein Idempotency-Key pro Aktion. Speichern Sie ihn zusammen mit dem Ergebnis; erscheint derselbe Key erneut, geben Sie dasselbe Ergebnis zurück.
Achten Sie auch auf Timeouts. Ein häufiger Fehler: Der Server bearbeitet noch, der Client timeoutet und retryt. Ohne Dedupe haben Sie zwei Bestellungen, zwei Passwort-Reset-Mails oder zwei „Willkommen“-Nachrichten.
Eine kurze Liste von Schutzmaßnahmen, die sich meist auszahlen:
- Fordern Sie bei Create/Submit-Endpunkten einen Idempotency-Key an und deduplizieren anhand (user, key)
- Verwenden Sie konsistente Fehlerantworten, damit Clients nur bei sicheren Fehlern retryen
- Loggen Sie request ID und Idempotency-Key bei jedem Versuch
- Führen Sie Side-Effects erst aus, nachdem Sie das „Diese Aktion ist passiert“-Record persistiert haben
- Behandeln Sie „bereits erledigt“ als Erfolg, nicht als gefährlichen Fehler
Verhindern Sie, dass parallele Requests Zustand überschreiben
Überschreibungen passieren, wenn zwei Requests denselben alten Zustand lesen und beide schreiben. Beispiel: Zwei Tabs bearbeiten ein Profil und der letzte Write gewinnt, wodurch die Änderung des anderen unbemerkt verloren geht.
Bevorzugen Sie serverseitige Prüfungen wie Versionsnummern (optimistisches Locking) oder explizite Regeln wie „nur aktualisieren, wenn Status noch PENDING ist“. Wenn Sie KI-generierte Handler geerbt haben, die Read-Modify-Write ohne Schutz machen, ist dies ein häufiger Grund für zufällige Nutzerberichte wie „manchmal speichert es, manchmal nicht".
Zustandsupdates: Überschreibungen und inkonsistente Daten verhindern
Viele flüchtige Verhaltensweisen sind nicht „async“ in der Queue oder im Netzwerk. Es sind zwei Teile Ihrer App, die dasselbe Stück Zustand in unterschiedlicher Reihenfolge aktualisieren. Mal gewinnt Request A, mal Request B.
Das klassische Problem ist das Lost-Update: Zwei Worker lesen denselben alten Wert, berechnen beide einen neuen Wert und der letzte Write überschreibt den ersten. Beispiel: Zwei Geräte ändern die Benachrichtigungseinstellungen eines Nutzers. Beide lesen „aktiviert“, eines schaltet ab, das andere ändert den Ton und der finale Datensatz verliert unausgesprochen eine Änderung.
Wann immer möglich, bevorzugen Sie atomare Operationen gegenüber Read-then-Write. Datenbanken bieten oft sichere Primitiven wie Increment, Compare-and-Set und „UPDATE WHERE version = X“. Das macht Timing zu einer klaren Regel: Nur ein Update kann erfolgreich sein und der Verlierer versucht es mit frischen Daten erneut.
Eine zweite Maßnahme ist die Validierung von Zustandsübergängen. Wenn eine Bestellung nur von pending -> paid -> shipped wechseln darf, lehnen Sie shipped -> paid ab, selbst wenn es spät ankommt. Das verhindert, dass verzögerte Requests, Retries oder Hintergrundjobs den Zustand zurückdrehen.
Caching kann das Problem verschlimmern. Ein veralteter Cache-Read kann ein „korrektes“ Write auslösen, das auf alten Daten basiert. Wenn Sie Zustand cachen, der Schreibentscheidungen antreibt, sorgen Sie entweder dafür, dass der Cache bei Updates invalidiert wird, oder lesen Sie die Quelle der Wahrheit kurz vor dem Schreiben.
Eine einfache Methode, Race Conditions zu beheben, ist Ownership: Legen Sie fest, wer ein Stück Zustand eindeutig aktualisieren darf, und routen Sie alle Änderungen darüber. Gute Ownership-Regeln sehen so aus:
- Ein Service besitzt das Schreiben, andere fordern nur Änderungen an
- Eine Tabelle oder ein Dokument ist die Quelle der Wahrheit
- Updates enthalten eine Versionsnummer (oder Zeitstempel) und werden bei Stale abgelehnt
- Nur erlaubte Zustandsübergänge werden akzeptiert
Bei FixMyMess sehen wir oft KI-generierten Code, der denselben Datensatz gleichzeitig aus UI-Code, API-Handlern und Hintergrundjobs aktualisiert. Ownership explizit zu machen ist meist der schnellste Weg, inkonsistente Daten zu stoppen.
Häufige Fallen, die flüchtige Bugs am Leben erhalten
Der schnellste Weg, eine Woche mit flüchtigen asynchronen Bugs zu verschwenden, ist, die Uhr zu „behandeln“ statt die Ursache. Wenn der Bug vom Timing abhängt, können Sie ihn einen Tag verschwinden lassen und ihn trotzdem in Production deployen.
Ein häufiger Fehler ist, mehr Retries hinzuzufügen, ohne Idempotenz. Wenn ein Zahlungs-Job zur Hälfte fehlschlägt und dann retried wird, könnten Sie doppelt belasten. Retries sind nur sicher, wenn jeder Versuch erneut ausgeführt werden kann, ohne das Ergebnis zu verändern.
Eine andere Falle ist, zufällige Delays einzustreuen, um Kollisionen zu „verteilen“. Das versteckt das Problem oft in Staging und verschlechtert die Lage in Production, weil sich Lastmuster ändern. Delays machen Ihr System auch langsamer und schwerer nachvollziehbar.
Große Locks können ebenfalls nach hinten losgehen. Ein globaler Mutex um den ganzen Ablauf kann die Flake stoppen, aber neue Fehler erzeugen: lange Wartezeiten, Timeouts und kaskadierende Retries, die den Bug in anderer Form zurückbringen.
Achten Sie auf Muster, die nicht-deterministisches Verhalten am Leben halten:
- Jeden Fehler als retrybar behandeln (Timeouts, Validation-Errors, Auth-Fehler und Conflicts brauchen unterschiedliche Handhabung)
- Sieg erklären, weil es lokal einmal lief (echte Concurrency zeigt sich selten auf einem ruhigen Laptop)
- Nur „Fehler ist passiert“ loggen ohne request/job id, Attempt-Nummer oder State-Version
- Symptome in einer Schicht fixen, während das Rennen darunter weiterexistiert (UI, API und Worker können sich überlappen)
- „Temporäre“ Hacks, die permanent werden (extra Retries, Sleeps oder catch-all Exception-Blöcke)
Wenn Sie einen KI-generierten Codebestand mit solchen Pflastern geerbt haben, zeigt ein fokussiertes Audit schnell, wo Retries, Locks und fehlende Idempotenz das eigentliche Rennen verbergen. Dann schlägt eine gezielte Reparatur Raten und Rätselraten.
Kurze Checkliste vor dem Release
Bevor Sie es als erledigt betrachten, stellen Sie sicher, dass Sie nicht nur „die Timing-Lotterie gewonnen“ haben. Ziel ist, Race Conditions so zu beheben, dass dieselben Eingaben jedes Mal dasselbe Ergebnis erzeugen.
Eine kurze Pre-Ship-Checkliste, die die meisten wiederkehrenden Fehler einfängt:
- Run it twice on purpose. Führen Sie die gleiche Aktion absichtlich zweimal aus (Doppelklick, zwei Worker, zwei Tabs) und bestätigen Sie, dass das Ergebnis korrekt ist, nicht nur „nicht kaputt“.
- Finden Sie das eine gemeinsame Ding. Identifizieren Sie die gemeinsame Ressource (Row, File, Cache-Key, Saldo, Inbox) und entscheiden Sie, wie sie geschützt ist: Transaktion, Lock, Unique Constraint oder bedingtes Update.
- Auditieren Sie Retries auf Side-Effects. Führt ein Retry zu einer weiteren E-Mail, doppelter Belastung oder einem duplizierten Row? Falls ja, fügen Sie Idempotenz hinzu, sodass „gleiche Anfrage" = "gleiche Wirkung".
- Vergleichen Sie die Reihenfolge in den Logs. Haben gute vs. schlechte Läufe unterschiedliche Ereignisreihenfolgen (Job startet bevor Record existiert, Callback vor dem Speichern)? Reihenfolgeunterschiede deuten stark darauf hin, dass Sie Symptome patchen statt Ursache zu beheben.
- Bevorzugen Sie atomare Garantien gegenüber Sleeps. Wenn eine Transaktion, ein Unique-Index oder ein „Update only if version matches" das Problem beseitigt, ist das meist sicherer als Verzögerungen.
Beispiel: Wenn „Subscription erstellen" manchmal doppelt belastet, vergewissern Sie sich, dass der Aufruf beim Payment-Provider durch ein Idempotency-Token abgesichert ist und Ihr Datenbank-Write durch eine Unique-Constraint auf diesem Token geschützt ist. So werden Duplikate zu harmlosen No-Ops statt zu Support-Feuern.
Beispiel: Einen flüchtigen Workflow Ende-zu-Ende stabilisieren
Stellen Sie sich einen Workflow vor: Zwei Teammitglieder bearbeiten denselben Kunden-Datensatz und ein Hintergrundjob aktualisiert denselben Datensatz nach einem Import. In Demos sieht alles gut aus, aber bei echten Nutzern treten merkwürdige Ergebnisse auf.
Aktuell verwendet die App „last write wins“. Nutzer A speichert, dann speichert Nutzer B etwas später und überschreibt A ohne Meldung. Gleichzeitig retried der Queue-Job nach einem Timeout und verschickt die „Kunde aktualisiert“-Benachrichtigung zweimal.
Um zu bestätigen, dass es nicht-deterministisch ist (und um Race Conditions zu beheben statt zu raten), bauen Sie ein reproduzierbares Szenario:
- Öffnen Sie den Datensatz in zwei Tabs (Tab A und Tab B)
- Ändern Sie in jedem Tab unterschiedliche Felder
- Klicken Sie in beiden Tabs innerhalb einer Sekunde auf Speichern
- Triggern Sie den Hintergrundjob und erzwingen Sie einen Retry (z. B. indem Sie vorübergehend einen Fehler nach dem Versenden werfen)
- Prüfen Sie den finalen Zustand des Datensatzes und die Anzahl der Notifications
Wenn jeder Lauf unterschiedlich endet, haben Sie das Timing-Problem gefunden.
Stabilisieren erfordert meist zwei Änderungen. Erstens: Machen Sie Notifications idempotent — fügen Sie einen Idempotency-Key wie customerId + eventType + version hinzu und speichern Sie ihn, damit dieselbe Notification nicht zweimal gesendet wird, selbst wenn ein Job retried wird.
Zweitens: Machen Sie das Record-Update atomar. Verpacken Sie das Update in eine Datenbank-Transaktion und fügen Sie eine Versionsprüfung (optimistisches Locking) hinzu. Wenn Tab B versucht, eine alte Version zu speichern, geben Sie eine klare Meldung zurück: „Dieser Datensatz hat sich geändert, bitte aktualisieren und erneut versuchen“, anstatt stillschweigend zu überschreiben.
Testen Sie das gleiche Repro 50 Mal. Sie wollen in jedem Lauf identische Ergebnisse: einen finalen Zustand, den Sie erklären können, und genau eine Notification.
Solche Probleme sieht FixMyMess oft in KI-generierten Apps: Retries und asynchrone Logik existieren, aber die Schutzmaßnahmen (Idempotenz, Locking, Transaktionen) fehlen.
Nächste Schritte: Das System wieder vorhersagbar machen
Wählen Sie die Flows aus, bei denen Flakiness am meisten schadet. Starten Sie nicht mit „der ganzen App“. Beginnen Sie mit 2–3 kritischen Abläufen, bei denen ein schlechtes Ergebnis teuer ist, z. B.: Abbuchungen, Kontoerstellung, Bestellaufgabe, E-Mail-Versand oder Inventar-Updates.
Schreiben Sie diese Flows in einfachen Schritten auf (was triggert es, was wird aufgerufen, welche Daten ändern sich). Diese kleine Landkarte gibt Ihnen eine gemeinsame „Wahrheit“, wenn Leute über Timing diskutieren. Sie macht es auch einfacher, Race Conditions gezielt zu beheben.
Wählen Sie eine Schutzmaßnahme, die Sie diese Woche ausrollen können. Kleine Änderungen reduzieren oft den größten Teil des Risikos:
- Fügen Sie einen Idempotency-Key für Aktionen hinzu, die etwas erstellen (Zahlungen, Bestellungen, E-Mails)
- Verwenden Sie ein bedingtes Update (nur aktualisieren, wenn die Version passt oder der Status noch erwartungsgemäß ist)
- Fügen Sie eine Unique-Constraint hinzu, damit Duplikate schnell und sicher fehlschlagen
- Erzwingen Sie Reihenfolge für ein Queue-Topic (oder bündeln Sie mehrere Jobs in einen „latest state“-Job)
- Setzen Sie Timeouts und Retry-Limits, wo Retries aktuell endlos laufen
Wenn Ihr Codebase von KI-Tools generiert wurde und schwer zu verstehen ist, planen Sie einen fokussierten Cleanup: ein Flow, ein Owner und eine Woche, um versteckten Shared State und „magische“ Retries zu entfernen.
Beispiel: Wenn „Bestellung erstellen" manchmal zwei Bestätigungs-E-Mails sendet, machen Sie zuerst das E-Mail-Senden idempotent und härten dann den Queue-Worker ab, sodass er gefahrlos retryen kann, ohne das Ergebnis zu ändern.
Wenn Sie einen schnellen, klaren Plan wollen, kann FixMyMess ein kostenloses Code-Audit durchführen, um Race Conditions, Retries und unsichere Zustandsaktualisierungen zu finden. Und falls Sie sofort Stabilisierung brauchen, können wir KI-generierte Prototypen innerhalb von 48–72 Stunden mit fachlicher Verifizierung für die Produktion vorbereiten.