Playbook

Das Outbox/Inbox-Muster in Zahlungssystemen (Outbox Inbox Muster In Zahlungssystemen)

Sind Datenbank-Schreibvorgang und Event-Publish nicht in derselben Transaktion, geht eines verloren oder verdoppelt sich. Outbox veröffentlicht, Inbox…

Verteilte Payment Engine (Distributed Payment Engine)

Teil 7 von 22

Serie zur verteilten Payment-Architektur — die Lücke zwischen Capture und Complete.

Distributed payment engine architecture diagram

Zwei Schreibvorgänge, keine gemeinsame Transaktion

Entscheidet ein Service „die Bestellung ist erfasst“, will er meist zwei Dinge tun: eine Zeile in seine Datenbank schreiben und ein Ereignis an eine Queue oder einen Broker veröffentlichen. Da diese beiden Schreibvorgänge an unterschiedliche Systeme gehen, können sie nicht in einer einzigen Transaktion atomar gemacht werden. Die Lücke dazwischen heißt Dual-Write-Problem.

DbContext.SaveChanges()      → in die Datenbank geschrieben
Broker.Publish(event)        → schlägt das fehl, geht das Ereignis verloren
(oder umgekehrt: das Ereignis wird gesendet, aber die DB-Transaktion wird zurückgerollt)

Dieser Teil behandelt die Outbox- und Inbox-Muster, die diese Lücke schließen.

Begriffe, dort definiert, wo sie zuerst auftauchen

📦 Dual-Write-Problem
Der Versuch, dieselbe Geschäftsentscheidung mit zwei nicht-atomaren Schreibvorgängen in zwei verschiedenen Systemen (DB und Broker) abzubilden.

📦 Outbox
Eine Ereignistabelle, die in derselben Datenbank-Transaktion wie die Geschäftsentscheidung geschrieben und später von einem separaten Prozess veröffentlicht wird.

📦 Lease
Eine temporäre Sperre, die verhindert, dass mehrere Publisher-Prozesse dieselbe Outbox-Zeile gleichzeitig aufnehmen.

📦 Inbox
Eine Tabelle auf Konsumentenseite, in der ein eingehendes Ereignis vor der Verarbeitung erfasst wird und Duplikate herausfiltert.

📦 Effectively-once
Das Ergebnis der Kombination von At-least-once-Zustellung mit einem idempotenten Konsumenten — verhält sich in der Praxis wie „genau einmal“.

Outbox löst das Problem des Senders (Atomizität von Schreiben und Publish). Inbox löst das Problem des Empfängers (erneute Zustellung). Gemeinsam bauen sie einen zuverlässigen End-to-End-Ereignisfluss auf.

Wie sich das Dual-Write-Loch öffnet

Schreibt ein Handler in die Datenbank und veröffentlicht direkt danach ein Ereignis an den Broker, kann das System zwischen diesen beiden getrennten Schritten abstürzen. Gelingt der DB-Schreibvorgang, schlägt aber der Broker-Aufruf fehl, wird die Geschäftsentscheidung dauerhaft, aber das Ereignis wird nie veröffentlicht — nachgelagerte Services erfahren nie davon.

1. DB: Order.Status = Captured  ✓ (committed)
2. Broker.Publish(OrderCaptured)  ✗ (Netzwerkfehler, Prozessabsturz)

Ergebnis: Die Orders-Tabelle zeigt Captured, aber kein Service erhielt ein Ereignis

Auch das Umgekehrte kann passieren: Das Ereignis wird veröffentlicht, aber die DB-Transaktion wird zurückgerollt — nachgelagerte Services reagieren dann auf etwas, das nie wirklich passiert ist.

Outbox: Schreiben und Publish in dieselbe Transaktion verschieben

Das Outbox-Muster schreibt das Ereignis, statt es direkt an den Broker zu veröffentlichen, in dieselbe Datenbank-Transaktion wie die Geschäftsentscheidung, in eine Outbox-Tabelle. Diese Zeile ist nun atomar mit der Geschäftsentscheidung dauerhaft — entweder committen beide, oder keines.

Eine Transaktion:
  UPDATE orders SET status = 'Captured' WHERE id = 42;
  INSERT INTO outbox (event_type, payload, dispatched_at) VALUES ('OrderCaptured', ..., NULL);
COMMIT

Ein separater Publisher-Prozess durchsucht die Outbox periodisch nach Zeilen mit dispatched_at IS NULL, veröffentlicht sie an den Broker und trägt bei Erfolg dispatched_at ein. Läuft dieser Publisher als mehrere Instanzen, verhindert ein Lease (eine kurzzeitige Sperre), dass zwei Instanzen dieselbe Zeile gleichzeitig aufnehmen.

Publisher A: least Zeile #7 (30s) → veröffentlicht an den Broker → dispatched_at = now()
Publisher B: Zeile #7 ist geleast, überspringt sie; sucht eine andere Zeile

Dieser Schritt garantiert, dass das Ereignis mindestens einmal veröffentlicht wird — stürzt der Publisher jedoch ab, kann es auch zweimal veröffentlicht werden. Deshalb braucht auch die Konsumentenseite ihre eigene Verteidigung.

Inbox: Deduplizierung auf Konsumentenseite

Da Outbox nur eine At-least-once-Veröffentlichung garantiert, kann der Konsument dasselbe Ereignis mehrfach erhalten. Statt das Ereignis direkt zu verarbeiten, schreibt das Inbox-Muster es zuerst unter einer eindeutigen Event-ID in eine Inbox-Tabelle; existiert dieser Eintrag schon (Unique-Constraint-Verletzung), wird das Ereignis nicht erneut verarbeitet.

Konsument empfängt Ereignis: event_id=evt_001
  INSERT INTO inbox (event_id, status) VALUES ('evt_001', 'received')
  → erfolgreich: Arbeit wird einem Job zugewiesen
  → Unique-Constraint-Fehler: bereits gesehen, wird stillschweigend übersprungen

Outbox + Inbox = Effectively-once

Outbox garantiert, dass die Veröffentlichung nie verloren geht (At-least-once). Inbox garantiert, dass dieselbe Veröffentlichung auf Konsumentenseite nie zweimal verarbeitet wird (idempotenter Konsument). Kombiniert verhält sich das System in der Praxis wie „genau einmal“ — das nennt man Effectively-once; eine echte Exactly-once-Garantie ist in verteilten Systemen praktisch unmöglich, aber diese Kombination ist in der Praxis eine ausreichende Annäherung.

Outbox (Sender)    → At-least-once-Veröffentlichung
Inbox (Empfänger)  → idempotenter Konsum
─────────────────────────────────────────
Gesamtverhalten    → Effectively-once

Die am häufigsten verwechselten Zuordnungen

❌ In die DB schreiben und sofort danach an den Broker publizieren ist sicher
✓ Diese zwei Schritte sind nicht atomar; dazwischen liegt das Dual-Write-Loch

❌ Outbox allein verhindert erneute Zustellung
✓ Outbox garantiert At-least-once; gegen erneute Zustellung braucht die Konsumentenseite eine Inbox

❌ Ein Lease ist eine dauerhafte Sperre
✓ Ein Lease ist temporär und zeitlich begrenzt; stürzt der Publisher ab, wird die Zeile nach Ablauf wieder frei

❌ Effectively-once ist dasselbe wie Exactly-once
✓ Exactly-once ist in verteilten Systemen unpraktisch; Effectively-once ist das Ergebnis von At-least-once plus Idempotenz

Checkliste zur Prüfung Ihres Outbox/Inbox-Setups

  1. Liegen der DB-Schreibvorgang Ihrer Geschäftsentscheidung und die Event-Veröffentlichung in derselben Transaktion, oder sind es zwei getrennte Schritte?
  2. Läuft Ihr Outbox-Publisher als mehrere Instanzen — haben Sie einen Lease-Mechanismus, der verhindert, dass zwei Instanzen dieselbe Zeile aufnehmen?
  3. Stürzt der Publisher ab, wird die Zeile nach Ablauf des Leases erneut versucht, oder bleibt sie für immer „geleast“?
  4. Hat Ihre Inbox-Tabelle auf Konsumentenseite einen Unique Constraint auf die Event-ID?
  5. Überwachen Sie die Anzahl der Zeilen in der Outbox mit dispatched_at IS NULL (ein Signal für Veröffentlichungsverzögerung)?

Können Sie zwei dieser fünf Fragen nicht sicher beantworten, ist Ihr System wahrscheinlich noch dem Dual-Write-Loch ausgesetzt.

Was aus diesem Teil bleiben sollte

  1. Ein Datenbank-Schreibvorgang und eine Event-Veröffentlichung gehen an unterschiedliche Systeme und sind nicht atomar; diese Lücke ist das Dual-Write-Loch.
  2. Outbox sichert die Atomizität auf Senderseite, indem das Ereignis in derselben Transaktion wie die Geschäftsentscheidung geschrieben wird; ein separater Publisher-Prozess veröffentlicht es.
  3. Ein Lease verhindert, dass mehrere Publisher-Instanzen dieselbe Outbox-Zeile gleichzeitig verarbeiten; er ist zeitlich begrenzt, nicht dauerhaft.
  4. Inbox verhindert, dass dasselbe Ereignis auf Konsumentenseite zweimal verarbeitet wird; sie macht die At-least-once-Garantie von Outbox idempotent.

Ein ohne Outbox veröffentlichtes Ereignis kann in dem Moment verschwinden, in dem es gesendet wird. Ein ohne Inbox empfangenes Ereignis kann zweimal verarbeitet werden und den Verlust doppelt so teuer machen.

FAQ

Häufige Fragen

Was ist Dual-Write-Problem?

Der Versuch, dieselbe Geschäftsentscheidung mit zwei nicht-atomaren Schreibvorgängen in zwei verschiedenen Systemen (DB und Broker) abzubilden.

Was ist Outbox?

Eine Ereignistabelle, die in derselben Datenbank-Transaktion wie die Geschäftsentscheidung geschrieben und später von einem separaten Prozess veröffentlicht wird.

Stimmt es, dass „In die DB schreiben und sofort danach an den Broker publizieren ist sicher“?

Diese zwei Schritte sind nicht atomar; dazwischen liegt das Dual-Write-Loch

Was legt dieser Teil fest?

Dieser Teil behandelt die Outbox- und Inbox-Muster, die diese Lücke schließen. Ein Datenbank-Schreibvorgang und eine Event-Veröffentlichung gehen an unterschiedliche Systeme und sind nicht atomar; diese Lücke ist das Dual-Write-Loch. Entscheidet ein Service „die Bestellung ist erfasst“, will er meist zwei Dinge tun: eine Zeile in seine Datenbank schreiben und ein Ereignis an eine Queue oder einen Broker veröffentlichen. Da diese beiden Schreibvorgänge an unterschiedliche Systeme gehen, können sie nicht in einer einzigen Transaktion atomar gemacht werden. Die Lücke dazwischen heißt Dual-Write-Problem.

Gelernte Engineering-Prinzipien

  • Liegen Datenbank-Schreibvorgang und Event-Veröffentlichung nicht in derselben Transaktion, bleibt das Dual-Write-Loch offen; Outbox schließt es auf Senderseite.
  • Inbox ist die notwendige Ergänzung, die die At-least-once-Garantie von Outbox auf Konsumentenseite idempotent macht.
  • Effectively-once ersetzt nicht Exactly-once; es ist das praktische Ergebnis von At-least-once-Zustellung kombiniert mit idempotentem Konsum.

Weiterlesen

Weiterlesen

Nachster Teil der Serie

ESSAY

Beleg (Evidence) ist, was der PSP sagte. Status (State) ist, was Sie entschieden haben. Vermischen Sie beide, weiß die Wiederherstellung nicht, wem sie…

Nachster Teil der Serie

Aus derselben Serie

Paylaş