Articles

What Is the Outbox Pattern in Microservices?

The outbox pattern writes an event to the same database transaction as a state change, then publishes it reliably — solving the dual-write problem.

Chisato Chisato · · 4 min read
Rows of server racks in a data center

The outbox pattern is a technique for reliably publishing events from a service that also needs to update its own database — by writing the event to an “outbox” table in the same local transaction as the state change, then having a separate process relay that event to a message broker. It solves a specific and easy-to-miss reliability problem: the dual-write problem.

The dual-write problem

Imagine an order service that, when a customer places an order, needs to do two things: save the order to its database, and publish an OrderPlaced event so other services (inventory, billing, notifications) can react to it.

The naive approach looks like this:

  1. Write the order to the database.
  2. Publish the event to a message broker like Kafka or a queue.

The problem is that these are two separate systems with no shared transaction. If step 1 succeeds but the service crashes before step 2, the order exists but no downstream service ever finds out — inventory never decrements, billing never fires. If you flip the order and publish first, you risk announcing an order that never actually got saved. There’s no way to make a database write and a message publish atomic across two different systems using ordinary means.

This is a variant of the idempotency and consistency problems that show up throughout distributed systems: any time you need “these two things happen together, or neither does” across a system boundary, you need a specific strategy for it. Two-phase commit is one classic answer for coordinating writes across systems, but it requires the message broker to participate in the same distributed transaction — most brokers don’t support that, and 2PC has its own availability costs.

How the outbox pattern solves it

The outbox pattern sidesteps the problem by keeping everything inside one database transaction:

  1. In a single local transaction, the service writes both the order row and an outbox row (containing the event payload — type, target, and data) to the same database.
  2. Because both writes are in one transaction against one database, they succeed or fail together. There’s no window where one happens without the other.
  3. A separate relay process — either a poller that periodically scans the outbox table for unpublished rows, or a change-data-capture connector reading the database’s write-ahead log — picks up new outbox rows and publishes them to the message broker.
  4. Once publishing is confirmed, the relay marks the outbox row as processed (or deletes it).

The atomicity that matters — order plus event, together or not at all — is guaranteed by the database’s own transaction handling, which every relational database already does well. The harder problem of getting the event onto the broker reliably is pushed into a separate, retryable step that can fail and retry without corrupting application state.

At-least-once delivery, not exactly-once

The outbox pattern guarantees the event will eventually be published if the local transaction commits, but it typically delivers at-least-once, not exactly-once. If the relay publishes an event and then crashes before marking it processed, it will republish that same event on restart.

This means consumers of outbox-published events need to be idempotent — processing the same event twice should produce the same result as processing it once. That usually means designing consumers to check whether they’ve already handled a given event ID before acting on it, rather than assuming delivery is deduplicated for them.

Outbox pattern vs change data capture

There are two common ways to implement the relay half of the pattern.

Polling relayChange data capture (CDC)
How it reads the outboxPeriodic query for unprocessed rowsTails the database’s write-ahead/binlog
LatencyBound by poll intervalNear real-time
Load on the databaseExtra queries on a scheduleReads a log stream, lighter on the query planner
Operational complexitySimple to build in-houseUsually needs a CDC tool (e.g. Debezium)

CDC-based relays are more common in production systems at scale because they avoid the added query load and latency of polling, but a simple polling relay is often good enough for lower-throughput services and is much easier to reason about and debug.

When to reach for it

The outbox pattern is worth adopting whenever a service both owns data and needs to notify other services about changes to that data — the classic shape of an event-driven microservices architecture. It’s overkill for a monolith or for services that don’t publish events at all.

It also pairs naturally with the saga pattern for coordinating multi-step business transactions across services: each step in a saga can use an outbox to reliably emit the event that triggers the next step, without needing distributed transactions anywhere in the chain.

The takeaway

The outbox pattern turns an unreliable dual-write (database plus message broker) into a single reliable local write (database only), by parking the event in the same transaction as the state change and letting a separate relay handle publishing. It trades exactly-once delivery for at-least-once plus idempotent consumers, which is a trade almost every distributed system ends up making somewhere — the outbox pattern just makes that trade explicit and contained to one well-understood place instead of leaving it to chance.

Chisato Chisato · · 5 min read

Raft vs Paxos: Consensus Algorithms Compared

Raft and Paxos both let a distributed cluster agree on a value despite failures — Raft trades some flexibility for a design built to be understood.

#Distributed Systems #Computer Science #Databases
Chisato Chisato · · 4 min read

What Are Vector Clocks? Ordering Distributed Events

A vector clock is a per-node counter array that lets distributed systems tell whether one event happened before another, without a shared clock.

#Distributed Systems #Databases #Cloud
The Lycoris Team The Lycoris Team · · 4 min read

Quorum Consensus Explained: N, W, and R

Quorum consensus lets distributed databases tune consistency and availability by requiring reads and writes to touch overlapping subsets of replicas.

#Databases #Distributed Systems #Cloud