Transactional Outbox
Write your data and your event in one transaction, then publish reliably — no dual write.
By the end of this lesson, use the outbox pattern to publish events reliably without dual-write inconsistency.
You update the database and you need to publish an event (to Kafka, a queue, a search index). Doing both as separate operations is a dual write — if one succeeds and the other fails, your systems diverge. The transactional outbox fixes this: write the business change *and* the event to the same database in one transaction, then publish the event from the outbox table separately.
First, see the whole system. Then we’ll open it up.
One transaction, reliable publish
- A single transaction writes both the business data and an outbox row.
- A relay (or change-data-capture) reads the outbox and publishes to the broker, then marks the row published.
flowchart LR tx[One DB transaction] --> data[(Business tables)] tx --> outbox[(Outbox table)] relay[Relay / CDC] -->|reads outbox| broker[Message broker] relay -->|mark published| outbox
Because the event is committed atomically with the data, it can never be lost or emitted for an uncommitted change. A relay process (polling or change data capture on the DB log) publishes outbox rows at least once — so consumers must be idempotent. This is the standard way to bridge a database and a message broker consistently.
The outbox buys atomic, reliable event publishing at the cost of an extra table, a relay process, and at-least-once (not exactly-once) delivery, which pushes dedupe onto consumers. The alternative — dual writes — is simpler and inconsistent under failure.
What problem does the outbox specifically prevent?
The dual-write problem: it makes the data change and the event atomic, so they can't diverge when one write fails.