Skip to main content
Pattern

Transactional outbox

Problem

You need to atomically update your database AND publish an event to Kafka. Two-phase commit isn't available; publishing before the DB commit risks phantom events; publishing after risks lost events on crash.

Context

The dual-write problem: any two systems (DB + event bus, DB + cache) can't be updated atomically without shared transactions.

Solution

Write the event to an 'outbox' table in the same DB transaction as the state change. A separate relay process reads the outbox table and publishes to Kafka. Because the outbox write is in the same transaction, either both succeed or both fail. The relay ensures at-least-once delivery.

Trade-offs

  • Event publication is delayed by the relay's poll cycle (usually ms-s)
  • Requires an outbox table (extra schema)
  • Relay must handle at-least-once delivery — consumers must be idempotent
  • Ordering of events matches the transaction order — good
  • Adds latency to writes if the outbox table is large

Failure modes

  • Relay crashes mid-flush; some events published, offset not committed → duplicates on restart
  • Outbox table grows unbounded if relay is slow — needs cleanup
  • Idempotency in consumers is non-negotiable

When to use

  • Microservices where DB updates must reliably trigger events
  • You need at-least-once event delivery guaranteed to match DB state
  • You can afford ms-s of event publication latency

When NOT to use

  • Fire-and-forget events where loss is OK
  • Ultra-low-latency requirements — outbox adds polling delay