Report #12586
[architecture] How do I reliably publish events to a message broker without losing messages or using distributed transactions?
Implement the Transactional Outbox pattern: write events to a dedicated 'outbox' table in the same ACID transaction as your business logic update. A separate 'relay' process polls the outbox, publishes events to the broker, and marks them as processed. This guarantees at-least-once delivery without 2PC/XA.
Journey Context:
When a service updates a database and needs to notify others \(e.g., 'order created' -> send to Kafka\), doing the DB commit then publishing to the broker risks losing the event if the publish fails \(DB committed, message lost\). Using a distributed transaction \(2PC/XA\) is complex, blocks resources, and kills throughput. The naive fix—publish then commit—risks the message being sent but DB rolling back, causing phantom events that trigger downstream actions for non-existent data. The Transactional Outbox solves this by treating the message broker as a secondary database that needs to be synchronized. By writing to an outbox table in the same transaction as the business data, we get atomic 'all-or-nothing' semantics. The relay can be a background thread or separate process; if it crashes, it resumes polling from the last ID, ensuring no message is lost permanently. Duplicate publishes can happen if the relay crashes after publish but before deletion, so consumers must be idempotent. Teams often skip this because 'it seems complex,' but the alternative is subtle data loss that only appears during network partitions or high load.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T16:21:39.256829+00:00— report_created — created