Die Idempotenz-Engine: Transaktionale Integrität sicherstellen

SchemaBridge Team · 2025-12-22 · Idempotency, Consistency, Transactions

At-least-once-Ausführung, Eingangs-Deduplizierung und Schlüssel auf Zielseite: wo Duplikate in einem verteilten Workflow tatsächlich gestoppt werden – und welcher Teil Ihre Aufgabe ist.

Der Albtraum der Doppelbelastung: Warum verteilte Systeme Retries hassen

In unserer Welt instabiler Netzwerke und kurzlebiger Cloud-Ressourcen sind Ausfälle nicht nur häufig – sie sind ein Dauerzustand. Jeder Senior Engineer kennt den Albtraum der „verwaisten Aktion“: ein Szenario, das als kleiner Netzwerk-Schluckauf beginnt und als Datenkorruption oder finanzielle Haftung endet. Es ist das klassische Fehlerszenario verteilter Systeme:

1. Anfrage: Ihr Orchestrator sendet eine „Charge“-Anfrage an eine Payment-API oder eine „Ship“-Anfrage an einen Versanddienstleister.

2. Erfolg: Die Drittanbieter-API verarbeitet die Anfrage erfolgreich, belastet die Karte des Kunden oder druckt das Versandetikett.

3. Partition: Auf dem Rückweg tritt eine kurze Netzwerkstörung auf. Die Antwort der API – die entscheidende Bestätigung – erreicht Ihre Worker-Maschine nie.

4. Retry: Ihr Worker sieht einen Timeout und nimmt nach gängiger Praxis an, dass der Vorgang fehlgeschlagen ist. Er folgt seiner Retry-Policy und sendet die Anfrage erneut.

5. Duplikat: Weil die API für genau diese Absicht keine eindeutige Identität erhalten hat, verarbeitet sie die Anfrage erneut. Sie hält sie für eine neue Transaktion. Der Kunde wird doppelt belastet, oder für dieselbe Bestellung werden zwei Versandetiketten erzeugt.

Das ist kein Fehler der Code-Logik, sondern ein Fehler der Identität. Ohne eine Möglichkeit, eine bestimmte Absicht über Zeit und Raum hinweg eindeutig zu identifizieren, spielt Ihr System bei jedem erneuten Verbindungsversuch mit den Daten und dem Geld Ihrer Kunden.

Verteilte Transaktionen sind tot – es lebe die Idempotenz

In einer monolithischen Architektur verlassen wir uns auf Two-Phase Commit (2PC) oder global verteilte Locks, um Konsistenz sicherzustellen. Diese Werkzeuge erlauben es, mehrere Operationen als eine einzige Wahrheitseinheit zu behandeln. Doch in einer fragmentierten Welt aus SaaS-APIs, Serverless-Workern und polyglotten Microservices sind globale Transaktionen eine Illusion. Sie skalieren nicht, sie verursachen massive Latenz, und die meisten Drittanbieter unterstützen sie nicht. Sie erfordern ein „Sperren“ von Ressourcen, das über Organisationsgrenzen hinweg unmöglich ist.

Der einzig gangbare Weg zu verteilter Konsistenz ist Idempotenz. Mathematisch ist eine Operation idempotent, wenn sie mehrfach angewendet werden kann, ohne das Ergebnis über die erste Anwendung hinaus zu verändern. Algebraisch: f(x) = f(f(x)). Ingenieurtechnisch heißt das: Ihr System kann beliebig oft fehlschlagen und es erneut versuchen, und das Endergebnis ist immer das richtige.

Der ehrliche Vertrag: At-least-once, mit Schutzmechanismen

Seien wir präzise darin, was ein Orchestrator versprechen kann und was nicht. SchemaBridge liefert Arbeit at-least-once aus. Ein Worker kann abstürzen, nachdem der HTTP-Aufruf erfolgreich war, aber bevor er das Ergebnis festgehalten hat; eine Nachricht kann erneut zugestellt werden; ein Claim-Lease kann ablaufen und übernommen werden. In jedem dieser Fälle ist das sichere Verhalten, den Schritt erneut auszuführen, statt ihn zu verlieren – was bedeutet, dass ein Schritt in seltenen Fällen zweimal laufen kann.

Die Plattform sorgt dafür, dass dieses „selten“ so selten wie möglich ist, und zwar auf zwei Ebenen:

Ebene 1: Deduplizierung eingehender Trigger

Jeder Trigger eines Entry Points erhält eine Execution-ID, und standardmäßig wird diese ID aus dem Inhalt abgeleitet: ein deterministischer SHA-256 über den Workspace, den Entry-Point-Vertex und den Request-Payload. Dieselbe Webhook-Zustellung, die zweimal eintrifft, wird auf dieselbe Execution aufgelöst, statt eine zweite zu starten – genau das, was Sie wollen, wenn ein Absender es erneut versucht. Sie steuern die Identität über drei Stellschrauben:

| Stellschraube | Wo | Einsetzen, wenn |

| :--- | :--- | :--- |

| idempotencyFields | Konfiguration des Entry-Point-Vertex | Sie die Deduplizierung an einem Geschäftsidentifikator (z. B. order.id) statt am gesamten Body festmachen wollen |

| clientIdempotencyId | Query-Parameter des Trigger-Aufrufs | Der Aufrufer den Dedup-Schlüssel besitzt |

| forceNewExecution | Nur Testläufe und der Streaming-Execute-Aufruf (dort standardmäßig aktiv) | Nicht für Produktions-Trigger verfügbar — dort idempotencyFields oder pro Aufruf eine eindeutige clientIdempotencyId verwenden |

{
  "type": "ENTRY_POINT",
  "config": {
    "idempotencyFields": "order.id"
  }
}

Die Kehrseite ist ein echter Stolperstein: Wenn zwei legitime Läufe byte-identische Payloads tragen können („den nächtlichen Report mit leerem Body ausführen“), werden sie zu einem zusammengefasst, sofern Sie sich nicht dagegen entscheiden. Zeitgesteuerte (Cron-)Trigger sind die Ausnahme – jede Auslösung erhält eine frische Zufalls-ID, denn ein Zeitplan soll nie dedupliziert werden. Und beachten Sie, dass die explizite Deduplizierung ein Read-then-Write-Lookup ist, keine Eindeutigkeits-Constraint in der Datenbank – zwei wirklich gleichzeitige Trigger mit demselben Schlüssel können also beide durchkommen.

Ebene 2: Schutzmechanismen auf Schrittebene

Innerhalb eines Laufs wird ein Schritt, der bereits COMPLETED ist, nicht erneut ausgeführt; ein Worker muss einen Schritt atomar beanspruchen (DISPATCHED → EXECUTING), bevor er ihn ausführt; terminale Übergänge sind abgesichert; und eine Claim-Epoche schirmt einen Worker ab, dessen Lease übernommen wurde. Diese Schutzmechanismen stoppen die üblichen Duplikatpfade – aber keiner von ihnen kann in eine Drittanbieter-API hineingreifen und einen bereits erfolgten Aufruf rückgängig machen.

Ebene 3 gehört Ihnen: Der Schlüssel auf Zielseite

Diese letzte Lücke ist der Grund, warum der Verhaltensvertrag der Plattform es klar ausspricht: Machen Sie den Seiteneffekt am Ziel idempotent, und verlassen Sie sich nicht darauf, dass der Orchestrator ihn genau einmal ausführt.

Die gute Nachricht: Die meisten ernstzunehmenden APIs (Payment, Versand, Messaging) akzeptieren einen Idempotenzschlüssel, und ein Gateway-Schritt kann einen aus den Daten des Laufs abgeleiteten Schlüssel mitsenden. Ein Header-Wert kann in SchemaBridge ein Ausdruck über den Input des Schritts sein, sodass der Schlüssel aus einem stabilen Geschäftsidentifikator berechnet wird – niemals aus einem Zeitstempel oder einem Zufallswert:

{
  "type": "GATEWAY",
  "config": {
    "method": "POST",
    "url": "https://api.payments.example/v1/charges",
    "headers": {
      "Idempotency-Key": "= 'charge-' & payload.order.id",
      "Authorization": "Bearer {{ secrets.PAYMENTS_KEY }}"
    }
  }
}

Weil der Schlüssel nur von der Bestellung abhängt, sendet eine erneute Ausführung dieses Schritts – nach einer erneuten Zustellung, einem Retry durch einen Operator oder einem automatischen Überlast-Retry – denselben Schlüssel, und das Ziel liefert das erste Ergebnis zurück, statt erneut zu belasten. Eine Einschränkung: Start over (eine Wiederholung des gesamten Workflows) erzeugt eine neue Execution, aber wenn Ihr Schlüssel aus Geschäftsdaten statt aus der Execution-ID abgeleitet ist, bleibt er selbst dann stabil. Genau deshalb sind Geschäftsidentifikatoren bessere Schlüssel als Engine-Identifikatoren.

Umgang mit Legacy-APIs: Das „Read-Verify-Write“-Muster

Leider unterstützen viele Legacy-Systeme und Nischen-SaaS-Anbieter keine Idempotenzschlüssel. Sie haben keinen Idempotency-Key-Header. Für diese „nicht idempotenten“ Endpunkte können Sie aus gewöhnlichen Schritten eine Read-Verify-Write-Sequenz zusammensetzen:

1. Verifier-Schritt (Read): Ein Gateway-Schritt fragt zuerst das nachgelagerte System ab, ob der Datensatz bereits existiert (z. B. GET /orders?external_id=123, mit demselben Geschäftsidentifikator, den Sie als Schlüssel verwendet hätten).

2. Bedingte Verzweigung: Eine bedingte Kante prüft die Antwort. Existiert die Bestellung, nimmt der Lauf den Pfad „bereits erledigt“. Andernfalls geht es weiter.

3. Action-Schritt (Write): Nur wenn der Verifier nichts findet, fährt der Lauf mit dem eigentlichen Schreibvorgang fort (POST /orders).

Kennen Sie das Zeitfenster

Read-Verify-Write verkleinert das Duplikatfenster; es schließt es nicht. Stürzt ein Worker nach dem Schreiben, aber vor dem Festhalten des Erfolgs ab, wird der Write-Schritt für sich allein erneut ausgeführt – er geht nicht zurück, um zuerst erneut zu verifizieren. Suchen Sie daher bei einem Ziel ganz ohne Idempotenzunterstützung zusätzlich nach einer natürlichen Eindeutigkeits-Constraint auf Zielseite (eine external_id, die bei Konflikt abgelehnt wird), und behandeln Sie eine Konfliktantwort als „bereits erledigt“. Standardmäßig lässt ein 4xx einen Gateway-Schritt fehlschlagen; setzen Sie clientErrorMode auf complete, wird eine Client-Fehlerantwort zu Daten, anhand derer Ihr nächster Schritt routen kann.

Die Falle des Schlüsselmanagements: Warum selbstgebaute Idempotenz im großen Maßstab scheitert

Viele Engineering-Teams versuchen, eine „Idempotenztabelle“ in ihrer primären Datenbank aufzubauen. Das erzeugt drei kritische Probleme, die letztlich Geschwindigkeit und Zuverlässigkeit zunichtemachen:

1. Der Schreib-Engpass: Jeder einzelne API-Aufruf erfordert nun einen Datenbank-Schreibvorgang, um das Token festzuhalten. Unter hoher Last wird Ihre Idempotenztabelle zum Konfliktpunkt.

2. Aufräumkomplexität – das Müllproblem: Idempotenzschlüssel werden nicht für immer gebraucht. Sie benötigen einen Hintergrundprozess oder eine TTL (Time-To-Live), um alte Schlüssel zu entfernen. Ist Ihr Aufräumen zu aggressiv, riskieren Sie Doppelbelastungen bei langsamen, erneut versuchten Aufgaben. Ist es zu langsam, wächst die Tabelle unbegrenzt.

3. Die verteilte Zustandsabweichung: Was passiert, wenn der Datenbank-Schreibvorgang gelingt, der API-Aufruf aber fehlschlägt? Oder wenn Ihr Worker nach dem API-Aufruf abstürzt, aber bevor die Datenbank auf „Fertig“ aktualisiert werden kann? Sie landen bei einer Abweichung, die manuelles Eingreifen erfordert.

Der Ausweg ist keine klügere Tabelle auf Ihrer Seite, sondern das System, das den Seiteneffekt ausführt, die Deduplizierung übernehmen zu lassen. Ein Ziel, das einen Idempotenzschlüssel respektiert, hat Speicherung, Ablauf und das Absturzfenster für seine eigene Operation bereits gelöst – Ihre Aufgabe ist es, ihm einen stabilen Schlüssel zu senden.

Token-Erzeugung auf Client- vs. Serverseite

Wo sollte das Token erzeugt werden?

In einem SchemaBridge-Workflow sind die eigenen Daten des Laufs die Quelle der Absicht. Wird der Schlüssel dort abgeleitet, im Header des Gateway-Schritts, bleibt er über jeden Retry-Pfad stabil, den die Engine nehmen könnte.

Vergleichstabelle: Wo Duplikate gestoppt werden

| Duplikatquelle | Gestoppt durch | Wer es konfiguriert |

| :--- | :--- | :--- |

| Absender wiederholt denselben Webhook | Inhaltsabgeleitete Execution-ID / idempotencyFields / clientIdempotencyId | SchemaBridge, mit dem Schlüssel Ihrer Wahl |

| Schritt innerhalb eines Laufs erneut zugestellt | Schutzmechanismen auf Schrittebene (Kurzschluss bei Abgeschlossenem, atomarer Claim, Claim-Epoche) | SchemaBridge |

| Schritt läuft erneut, nachdem der Aufruf bereits erfolgreich war | Idempotenzschlüssel auf Zielseite | Sie, im Gateway-Schritt |

| Ziel hat keine Idempotenzunterstützung | Read-Verify-Write plus Eindeutigkeits-Constraint am Ziel | Sie |

Expertentipps: Die Idempotenz-Checkliste

1. Niemals Zeitstempel verwenden: Ihr Schlüssel muss auf den Daten basieren, nicht auf der Zeit.

2. Schlüssel mit Geltungsbereich versehen: Präfixieren Sie Schlüssel nach Operation (charge-, ship-), damit ein Schlüssel für „Versand“ nie mit einem Schlüssel für „Abrechnung“ kollidiert, selbst wenn beide dieselbe Bestell-ID teilen.

3. 409-Konflikte behandeln: Gibt eine API für einen bereits bekannten Schlüssel oder Identifikator ein 409 (Conflict) zurück, sollte Ihr Workflow dies in der Regel als „bereits erledigt“ behandeln, sofern der Input übereinstimmt.

4. Über wiederholte Payloads vorab entscheiden: Wenn zwei echte Läufe identische Bodies tragen können, setzen Sie idempotencyFields oder übergeben Sie pro Aufruf eine eindeutige clientIdempotencyId – andernfalls wird der zweite Lauf wegdedupliziert. (forceNewExecution gibt es nur bei Testläufen und beim Streaming-Execute-Aufruf.)

5. Den Schlüssel zum Teil der Vorlage machen: Wenn ein Entwickler daran denken muss, einen Idempotenzschlüssel hinzuzufügen, wird er es irgendwann vergessen. Legen Sie ihn in die Gateway-Konfiguration, die Sie für diese API wiederverwenden.

Fazit: Identität ist das Rückgrat der Wahrheit

In einem verteilten System können Sie dem Netzwerk nicht trauen, Sie können der Uhr nicht trauen, und Sie können der Antwort nicht trauen. Das Einzige, dem Sie wirklich trauen können, ist Identität.

SchemaBridge gibt Ihnen Identität an den Grenzen, die es kontrolliert – inhaltsabgeleitete Execution-IDs, explizite Dedup-Schlüssel und Schutzmechanismen auf Schrittebene – und ist ehrlich in Bezug auf die Grenze, die es nicht kontrolliert: den Seiteneffekt beim Drittanbieter. Leiten Sie einen stabilen Schlüssel aus Ihren Geschäftsdaten ab, senden Sie ihn bei jedem Aufruf mit, und der „Albtraum der Doppelbelastung“ wird zu einer Designentscheidung, die Sie einmal getroffen haben, statt zu einem Vorfall, den Sie später entdecken.

Teil unserer Serie „Building the Bridge“. Siehe auch: Den Merge meistern zur Synchronisierung von Zustand über parallele Zweige hinweg.

Ansehen