← Concepts

阅读中文版本

Transactional Outbox

The transactional outbox writes messages into the same database transaction as the state change, then relays them reliably.

database

Definition

The transactional outbox solves the dual-write problem: instead of writing to the database and publishing to a broker separately — where one may succeed and the other fail — the service writes both the business change and an outbox record in a single local transaction. A relay process reads the outbox and publishes each message, marking it sent. Atomicity comes from the database transaction; delivery to the broker becomes at-least-once, and the consumer must be idempotent.

Context and scope

Used whenever a service must both change its own state and notify other services, in event-driven and microservice architectures.

Common misconceptions

  • The outbox guarantees exactly-once delivery — it guarantees atomic enqueue; publishing and consumption are at-least-once, so consumer idempotency is still required
  • A message broker transaction removes the need — brokers and databases rarely share a transaction, which is the whole problem
  • The relay can publish in any order — outbox ordering must be preserved per aggregate to keep event sequences valid

Anti-patterns

  • Publishing to the broker first and writing to the database after, reversing the failure window
  • Deleting outbox rows before the broker confirms, losing messages on a crash
  • Ignoring consumer idempotency so retried deliveries duplicate side effects

Related concepts

Related examples

Authoritative source

https://microservices.io/patterns/data/transactional-outbox.html

What it is

The outbox pattern adds a table in the service own database. When a request changes business state, the same transaction inserts one or more outbox rows describing the events to emit. Because both writes share one transaction, they commit or roll back together, so there is never a state change without a corresponding queued message. Separately, a relay polls or streams the outbox, publishes each pending message, and marks it delivered.

Why it matters

Without it, a service must write to its database and publish to a broker, and any failure between the two loses synchronisation: the state changed but nobody was told, or a message announced a change that was rolled back. Retrying does not fix it because there is no distributed transaction across storage and messaging. The outbox collapses the dual write into one atomic local step, then handles the unreliable part — publishing — with a retrying relay.

How to use it well

Write outbox rows in the same transaction as the domain change, and have the relay publish then mark, never the reverse, so a crash re-publishes rather than drops. Preserve per-aggregate order by sequencing rows, and prune delivered rows so the table stays bounded. Because delivery is at-least-once, equip consumers with idempotency keys or deduplication so a repeated message does not repeat its side effect. Many teams replace the polling relay with change data capture on the outbox table.