Part 7 · 1 chapters · ~8 min

Outbox and Inbox

The dual-write problem, the transactional outbox with polling and CDC relays, ordering and cleanup of outbox rows, the inbox pattern for idempotent consumption, combining both for end-to-end exactly-once effect, and libraries that implement them.

10

Both ends of a reliable message

code
-- outbox (producer side)
BEGIN;
UPDATE payouts SET state = 'completed' WHERE id = $1;
INSERT INTO outbox (id, aggregate_id, type, payload) VALUES (gen_random_uuid(), $1, 'PayoutCompleted', $2);
COMMIT;
-- relay: SELECT ... FROM outbox WHERE published_at IS NULL ORDER BY created_at LIMIT 100 FOR UPDATE SKIP LOCKED; publish; mark published

-- inbox (consumer side)
BEGIN;
INSERT INTO inbox (message_id) VALUES ($msg_id) ON CONFLICT DO NOTHING;   -- 0 rows → duplicate, skip the effect
UPDATE loyalty_points SET points = points + 10 WHERE user_id = $u;
COMMIT;   -- then acknowledge
OUTBOX AND INBOX
reliable publishing out, reliable processing in
service transactionstate + outbox rowrelay / CDCpublish from outboxbrokerconsumerinbox table firsteffectin the same transactionack the message
swipe the figure sideways, or tap expand for full screen
1/4
the outbox
Writing state and publishing an event are two systems with no shared transaction (the dual-write problem). The outbox writes the event as a row in the same transaction as the state (Scaling course P7).
event row in the state transactionno dual write