Tacnode™
Glossary
Data Patterns

What Is Outbox Pattern?

The outbox pattern (transactional outbox) is a technique for reliably publishing events from a service that updates a database. Instead of writing to the database and the message broker as two separate operations, the service inserts the event into an outbox table within the same local transaction as the business data. A separate relay process then reads the outbox and publishes the events to the broker — guaranteeing an event is published if, and only if, the business change committed.

Updated

What is the outbox pattern?

The pattern exists because of the dual-write problem: a database and a message broker do not share a transaction, so writing to both independently means a crash between the writes leaves them inconsistent — a committed order with no event, or a delivered event for an order that never committed.

The outbox pattern removes the second write from the request path entirely. The only atomic operation is a single local ACID transaction covering both the business tables and the outbox table. Publication becomes an asynchronous, retryable background concern. The result is at-least-once delivery: every committed event is eventually published, possibly more than once, and never a phantom event for an uncommitted change.

Two relay styles are standard. A polling publisher queries the outbox table for unpublished rows on an interval. A log-tailing relay uses change data capture to stream outbox inserts directly from the database's transaction log — lower latency and no polling load, at the cost of running CDC infrastructure (Debezium's outbox router is the widely used reference implementation).

How the outbox pattern works

The write path is one transaction:

sql
BEGIN;

INSERT INTO orders (id, customer_id, status, total)
VALUES ('o-981', 'c-204', 'PLACED', 84.00);

INSERT INTO outbox (id, aggregate_id, event_type, payload)
VALUES (gen_random_uuid(), 'o-981', 'OrderPlaced',
        '{"orderId": "o-981", "total": 84.00}');

COMMIT;

Either both rows exist or neither does. The relay then reads new outbox rows in commit order, publishes each to the broker (typically keyed by aggregate_id so per-entity ordering survives partitioning), and marks or deletes the row. If the relay crashes after publishing but before marking, it republishes on restart — the source of at-least-once semantics. Consumers therefore deduplicate, usually by tracking processed event IDs or by making handlers idempotent.

Why the outbox pattern matters in real-time systems

The outbox pattern converts "events might be silently lost" into "events arrive, eventually, at least once" — a categorical improvement for anything downstream that must stay in sync with a system of record: caches, search indexes, analytics, fraud features, agent-facing context stores.

What it does not change is timing. The relay hop adds latency between commit and publication, and consumer processing adds more. Every downstream copy of the data trails the source by the sum of those delays, so a decision reading a downstream projection is reading the recent past — the general problem described in what is stale data. The outbox pattern is best understood as solving reliability of propagation, not freshness of propagation: it guarantees the pipeline converges, while the convergence lag remains a property the consuming system must budget for explicitly.

FAQ

Related terms

Further reading