System Design · Architecture under constraints
What is the transactional outbox pattern and why is it needed?
Short answer
The outbox pattern writes the state change and an event row in one database transaction, then relays the event. It removes the dual-write gap between committing data and publishing to a broker.
Publishing to a broker and committing to a database are two separate systems. If the commit succeeds and the publish fails, downstream consumers never see the event. If the publish succeeds and the commit fails, consumers act on something that never happened. The outbox removes this gap.
BEGIN;
UPDATE orders
SET status = 'CONFIRMED'
WHERE id = 42 AND status = 'PENDING';
INSERT INTO outbox (id, aggregate_id, event_type, payload, created_at)
VALUES ('evt_01', 42, 'OrderConfirmed', '{"orderId":42}', now());
COMMIT;How the relay works
- Polling publisher: a worker selects unpublished rows, publishes them, then marks them sent
- Change data capture: a log reader such as Debezium tails the database log and emits inserts
- The relay must tolerate publishing twice, because marking sent can fail after a successful publish
- Consumers must deduplicate by event id or make their effects idempotent
- Ordering is preserved only if the relay publishes in commit order for a given aggregate
The outbox table grows and needs cleanup. Delete or archive rows after they are confirmed sent, but keep them long enough for replay and audit. If the relay is down, events accumulate in the database, which is a visible backlog rather than silent loss. That is the main advantage over a best-effort publish.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1Why can the outbox not guarantee exactly-once delivery?
- 2How do you preserve ordering per aggregate?
- 3What are the trade-offs of polling versus change data capture?
- 4How long would you retain outbox rows?
Say it in the interview
The outbox pattern writes the business change and an event row in the same local transaction, so they either both commit or both fail. A relay then publishes committed rows, using polling or change data capture. It guarantees at-least-once, not exactly-once, so consumers still need deduplication by a stable event id. The trade-off is a table to clean up and a backlog that lives in the database instead of being silently lost.
Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.
Related questions
Reading is step one
Now say it out loud.
Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live System Design mock interview and get scored feedback on depth, clarity and confidence.