You Don't Need an Outbox

goloroden1 pts0 comments

You Don't Need an Outbox - EventSourcingDB

Skip to content

Initializing search

Getting Started

Fundamentals

Deployment and Operations

Reference

Client SDKs

Extensions

MCP Server

Best Practices

Implementation and Development

Data Management and Performance

Operations, Compliance and Infrastructure

Common Issues

Blog

Categories

Privacy Policy

Legal Notice

You Don't Need an Outbox¶

The outbox pattern has quietly become canonical in microservice tutorials. It has a name, library support, conference talks, and a steady stream of blog posts that walk through implementing it. It's blessed. And yet, every time it shows up in a system, it's a sign that something else has gone wrong upstream.

The pattern itself is clever. It works around a real, structural problem: two systems that need to agree but can't share a transaction. The question worth asking is whether you need to have that problem in the first place. Once you take that seriously, the outbox stops looking like a pattern and starts looking like an admission of defeat .

The Problem the Outbox Solves¶

Picture a typical service. It receives a command, writes some rows to its relational database, and publishes a domain event to a message broker so that other services can react. Two side effects, two systems, one logical operation.

What happens if the database commit succeeds but the broker write fails? You've changed state without telling anyone. The order is placed, but no OrderPlaced ever arrives downstream. The other direction is worse: you announce an event for state that the database eventually rolled back. Either way produces silent inconsistency, and debugging it requires reconstructing the timing of two systems that don't share a clock.

This is the dual-write problem . The outbox pattern is the standard solution. Inside the same transaction that writes the business state, you write a row into a local outbox table. A background process, sometimes called a relay or forwarder, polls or tails that table, sends each entry to the broker, and marks it as delivered.

Because the outbox row and the business state live in the same transaction, they live or die together. If the broker is down, no message is lost; the row simply waits. If the transaction rolls back, the row was never there to send. The dual-write becomes a single write followed by an asynchronous handoff. Done well, this gives you at-least-once delivery with transactional consistency to the database.

What the Outbox Actually Costs¶

If you've ever run an outbox in anger, you know it's not free.

Latency is the first cost. Events don't appear downstream when the transaction commits. They appear when the relay next polls the outbox, picks them up, and writes them to the broker. For high-throughput systems you crank polling intervals down, accept higher database load, and watch tail latency rise anyway.

Monitoring surface is the second. Outbox lag is a metric you didn't have before. So is broker write failure rate, dead-letter handling, and the inevitable runbook for "the outbox is backed up, what do we do?" The pattern produces its own operational ecosystem of dashboards, alerts, and on-call playbooks.

Ordering is the third, and it's the trap people fall into late. The naive outbox relay picks up rows in order and sends them in order. The moment you scale it horizontally for throughput, ordering across partitions is no longer guaranteed. You can shard by aggregate ID and preserve per-aggregate order, but the system-wide order that some consumers were quietly relying on is gone. Most teams discover this after a downstream service starts producing nonsense because it assumed a global order that never existed.

Schema duplication is the fourth. You now have at least two representations of every event: one in the outbox table, probably a JSON blob alongside columns for status, retries, and timestamps, and one on the wire, whatever Avro, Protobuf, or JSON schema the broker speaks. They must stay in sync, version compatibly, and roll back compatibly. Two places to change. Two places to break.

Idempotency is the fifth, and it's the one that always survives the rewrite. Outbox or not, your consumers must handle duplicates. The relay can deliver the same event twice if the network fails between sending and marking-as-delivered. The broker can deliver the same event twice. The consumer can process the same event twice. None of this changes, as we discussed in Exactly Once Is a Lie . The outbox doesn't remove the requirement. It just gives you another place to manage it.

The Hidden Assumption¶

The outbox pattern is a sensible answer to a specific question: how do I keep my CRUD database and my message broker in agreement?

That question contains the assumption. It assumes that business state lives in one system and events live in another , that the database is the source of truth and the broker is a side channel for telling other services what changed. Events,...

outbox broker database pattern event write

Related Articles