Artikel

Laravel und Kafka ohne verlorene Events: Transactional Outbox und idempotente Consumer mit PostgreSQL

Laravel und Kafka ohne verlorene Events: Transactional Outbox und idempotente Consumer mit PostgreSQL

Ein Datenbank-Commit und eine Kafka-Veröffentlichung sind keine normale gemeinsame Transaktion. Outbox und Duplikatschutz schließen die wichtigsten Fehlerfenster.

11 Min. LesezeitSprache: DE DeutschKostenlos0 Claps0 Kommentare
Leseoptionen

Dieser Code wirkt zunächst plausibel:

$order = Order::create($data);
$kafka->publish('orders.placed.v1', $order->toArray());

Gelingt die Datenbankänderung und scheitert der Publish, existiert eine Bestellung ohne Inventar-Event. Vertauschst du die Aufrufe, kann ein Event über eine später zurückgerollte Bestellung erscheinen. Ein Kafka-Aufruf innerhalb von DB::transaction() macht Kafka nicht zum Teil der PostgreSQL-Transaktion.

Die Transactional Outbox schreibt Geschäftsdaten und Event-Datensatz in derselben Datenbanktransaktion. Ein separater Publisher bringt den Datensatz nach Kafka. So übersteht das Event einen Prozessabsturz nach dem Commit. Weil die Veröffentlichung wiederholt werden kann, muss der Consumer Seiteneffekte idempotent behandeln.

1. Der Event-Vertrag

{
  "eventId": "evt_01JQ8X8F",
  "eventType": "OrderPlaced",
  "schemaVersion": 1,
  "occurredAt": "2026-09-29T10:30:00Z",
  "orderId": "ord_782",
  "customerId": "cus_17",
  "totalMinor": 12900,
  "currency": "USD"
}

OrderPlaced bedeutet, dass die Bestellung tatsächlich bestätigt wurde. eventId bleibt über Wiederholungen unverändert und dient der Duplikaterkennung. Der Kafka-Key ist orderId, sodass Ereignisse derselben Bestellung bei stabiler Partitionierung zusammenbleiben. Veröffentliche kein vollständiges Eloquent-Modell: Tabellenfelder, Beziehungen und interne Daten sind kein stabiler öffentlicher Vertrag.

Dokumentiere Eigentümer, Key, Felder, Aufbewahrung, Kompatibilität und geplante Leser des Topics. Ein v1 im Namen ersetzt keine Schema-Governance.

2. Outbox-Tabelle und eine Transaktion

Ein vereinfachter PostgreSQL-Startpunkt:

CREATE TABLE outbox_events (
  id uuid PRIMARY KEY,
  aggregate_id text NOT NULL,
  topic text NOT NULL,
  event_key text NOT NULL,
  payload jsonb NOT NULL,
  occurred_at timestamptz NOT NULL,
  published_at timestamptz NULL,
  attempts integer NOT NULL DEFAULT 0
);
CREATE INDEX outbox_pending_idx ON outbox_events (occurred_at, id)
  WHERE published_at IS NULL;

Erstelle Bestellung und Outbox-Zeile innerhalb derselben DB::transaction(). Bei Rollback verschwinden beide; nach Commit bleibt das Event selbst bei einem sofortigen Absturz erhalten. Halte die Transaktion kurz und warte nicht mit Datenbanksperren auf Kafka. Ein anpassbarer PHP-Entwurf:

DB::transaction(function () use ($data) {
    $order = Order::create($data);
    $eventId = (string) Str::uuid();
    DB::table('outbox_events')->insert([
        'id' => $eventId,
        'aggregate_id' => (string) $order->id,
        'topic' => 'orders.placed.v1',
        'event_key' => (string) $order->id,
        'payload' => json_encode([
            'eventId' => $eventId,
            'eventType' => 'OrderPlaced',
            'schemaVersion' => 1,
            'orderId' => (string) $order->id,
        ], JSON_THROW_ON_ERROR),
        'occurred_at' => now(),
    ]);
});

Im echten Projekt gehören Validierung, Datentypen und Datenschutz dazu. Das Beispiel ist kein universeller Aufruf einer bestimmten Kafka-PHP-Bibliothek.

3. Publisher und Fehlerfenster

Ein Worker wählt kleine Mengen unveröffentlichter Zeilen, beansprucht sie sicher gegenüber anderen Workern, sendet mit gespeichertem Key nach Kafka, wartet auf passende Bestätigung und setzt published_at. Ein kurzer Claim oder ein ablaufender Lease kann abgestürzte Worker auffangen. Halte keine Datenbanksperre während einer langsamen Netzwerkoperation.

Kafka kann das Event akzeptieren, bevor der Worker published_at setzt und abstürzt. Beim nächsten Versuch wird es nochmals gesendet. Die Outbox bietet daher mindestens einmalige Veröffentlichung, kein globales „genau einmal“. Verwende dieselbe eventId bei jedem Versuch. Ein idempotenter Producer hilft gegen eigene Kafka-Retries, macht PostgreSQL-Update und Kafka-Bestätigung aber nicht atomar.

Der Publisher kann ein Laravel-Command, Worker oder CDC-Prozess sein. Client-Bibliotheken brauchen eine Prüfung auf Version, Sicherheit und Betrieb. Laravels eingebaute Queue-Backends umfassen etwa Redis, SQS und Datenbanken; Kafka wird durch QUEUE_CONNECTION allein nicht zum eingebauten Driver.

4. Der idempotente Inventar-Consumer

Ein Consumer reserviert Bestand und stürzt vor seinem Offset-Commit ab. Nach dem Neustart liest er das Event erneut. Eine eindeutige Tabelle schützt den Seiteneffekt:

CREATE TABLE processed_events (
  consumer_name text NOT NULL,
  event_id text NOT NULL,
  processed_at timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (consumer_name, event_id)
);

Füge (inventory-reservation, eventId) in derselben Inventar-Datenbanktransaktion ein, die die Reservierung ausführt. Ist der Eintrag bereits vorhanden, überspringe den wiederholten Effekt. Erst nach erfolgreichem Commit schreitet der Kafka-Offset fort. Ein Absturz zwischen Datenbank- und Offset-Commit führt zu sicherer Wiederholung. Ein Rollback darf nicht als erfolgreich verarbeitet gelten.

E-Mail und Zahlung liegen außerhalb dieser lokalen Transaktion. Nutze nach Möglichkeit Idempotency-Keys des Anbieters, eine weitere Outbox oder eine Kompensation. Formuliere Garantien für jeden Seiteneffekt getrennt.

5. Retry, Quarantäne und Schemaänderungen

Ein vorübergehender Datenbankausfall braucht Retry mit Backoff. Ein dauerhaft ungültiges Event braucht Untersuchung, Reparatur oder Quarantäne. Ein Dead-Letter-Topic kann helfen, doch dessen Publish und das Commit des ursprünglichen Offsets haben wieder ein Fehlerfenster. Definiere Alarm, Zugang, Retention und Replay. Schlucke keine Ausnahme, um bloß den Offset fortzuschreiben.

Logge eventId, Topic, Partition, Offset, Gruppe und sichere Fehlerkategorie, ohne vertrauliche Payloads vollständig zu speichern. Halte Duplikatschutz für die versprochene Retention- und Replay-Zeit. Teste neue Producer-Schemas gegen alte Events und Consumer. Ändere bestehende Feldbedeutungen nicht stillschweigend.

Teste Ausfälle statt nur Methodenaufrufe

Teste Bestell-Rollback, Commit bei gestopptem Publisher, Kafka-Erfolg mit Absturz vor published_at, doppelte Lieferung, Absturz nach Reservierung und vor Offset sowie parallele Worker. Beobachte Alter der ältesten Outbox-Zeile, ausstehende Menge, Publish-Fehler, Consumer-Lag und Quarantäne. Ein gesunder HTTP-Endpunkt sagt nichts über eine feststeckende Outbox.

Für eine einzelne Hintergrund-Mail reicht oft die normale Laravel-Queue. Wenn ein Geschäftsfakt mehrere unabhängige Systeme zuverlässig erreichen und später erneut gelesen werden muss, bilden Outbox, stabile Event-ID, idempotenter Consumer und sichtbare Fehlerbehandlung eine sinnvolle Einheit.

Quellen

Empfohlene Artikel

Kafka im Produktivbetrieb: Partitionen, Consumer Lag und sichere Wiederherstellung
EditorialDE
11 Min.Kostenlos

Kafka im Produktivbetrieb: Partitionen, Consumer Lag und sichere Wiederherstellung

Von der Wahl des Partition Keys bis zum Incident Runbook: Dieser Leitfaden verbindet Kafka Kennzahlen mit den tatsächlichen Geschäftsergebnissen.

Engineering-ArtikelPlattform-Leitfäden
0 Claps
Lesen
Apache Kafka verstehen: Topics, Partitionen, Consumer Groups und deine erste Event-Pipeline
EditorialDE
10 Min.Kostenlos

Apache Kafka verstehen: Topics, Partitionen, Consumer Groups und deine erste Event-Pipeline

Verfolge ein Bestellereignis vom Producer zu mehreren Consumern und probiere Partitionen, Reihenfolge und Replay lokal aus.

Engineering-ArtikelPlattform-Leitfäden
0 Claps
Lesen
APIs entwerfen, denen Clients vertrauen können: Ein praktischer Leitfaden vom Vertrag aus
EditorialDE
10 Min.Kostenlos

APIs entwerfen, denen Clients vertrauen können: Ein praktischer Leitfaden vom Vertrag aus

Gute APIs machen den nächsten Schritt für Clients vorhersehbar. Ein Kursanmeldungs-Beispiel zeigt sichere Wiederholungen, verständliche Fehler und Änderungen am Vertrag.

Engineering-ArtikelPlattform-Leitfäden
0 Claps
Lesen

Kommentare

0 Kommentare

Noch keine freigegebenen Kommentare sichtbar. Neue Antworten können moderiert werden.