Skip to main content

> change_data_capture_with_debezium_&_outbox_event_ordering

Change Data Capture with Debezium & Outbox Event Ordering

How do you implement and govern Change Data Capture with Debezium & Outbox Event Ordering in high-throughput production architectures?

Stack: SOFTWARE ARCHITECTURE STACKStaff/Principal (L6+)pattern

THE SHORT ANSWER

The CDC Outbox pattern combines the Transactional Outbox pattern with Debezium log mining to stream database mutations directly from the database's Write-Ahead Log (WAL) into partitioned Kafka topics, guaranteeing 100% reliable event delivery and strict per-entity causal ordering without dual-write race conditions.

Engineering Handbook & Failure Dynamics

1. Underlying Mechanism

Writing to a relational database and publishing to a Kafka topic in the same HTTP request is the classic 'Dual Write' anti-pattern: if the database commits but the network fails before Kafka publishes, events are lost forever. The CDC Outbox pattern solves this by writing the domain mutation and an outbox event in the same local SQL transaction. Debezium reads the PostgreSQL WAL via logical replication and streams the events to Kafka.

2. Appropriate Use Context

CDC Outbox is an architecture where an application atomically inserts domain events into an `outbox` table during local database transactions, and a Change Data Capture connector (Debezium) extracts and routes those events into Kafka by streaming the database transaction log.

3. Production Failure Modes

Querying the outbox table via periodic `SELECT * FROM outbox WHERE processed = false` polling at 100ms intervals, creating extreme database lock contention. Routing outbox events to Kafka with random or null message keys, scattering events for the same customer across multiple partitions and breaking causal ordering. Never deleting processed outbox rows, causing the outbox table to grow to 100M rows and exhausting disk storage.

4. Diagnostic Signals & Telemetry

dual-write desynchronization between database and Kafka, outbox events routed to wrong Kafka partition losing per-entity ordering, uncleaned outbox table causing massive database bloat

5. Prevention & Safeguards

Use Debezium with PostgreSQL Logical Replication (`pgoutput`) and WAL level set to `logical`. Set the Kafka message key to the domain entity ID to guarantee per-aggregate FIFO sequencing. Enable outbox table automatic row deletion (tombstoning) or periodic partition truncation to prevent table bloat.

6. Architectural Trade-offs

It completely eliminates dual-write data loss and ghost events, offloading the event-polling burden from the application CPU to the database's native asynchronous replication stream.

Case Study (TinyCTO In-Field Example)

A production CDC Outbox pipeline requires three critical configurations: 1. **Atomic Local Transaction:** The service writes to `orders` and `outbox_events` (`id`, `aggregatetype`, `aggregateid`, `type`, `payload`) in a single `BEGIN ... COMMIT` block. 2. **Debezium Outbox Event Router (SMT):** Debezium's Single Message Transform parses the outbox table change stream, extracts the `payload`, and routes the message to a dynamic destination topic (e.g. `order-events`). 3. **Causal Ordering via Kafka Partition Key:** To guarantee that `OrderUpdated` is never processed before `OrderCreated`, Debezium must set the Kafka Message Key to `aggregateid` (e.g. `order_uuid`). Kafka guarantees strict FIFO ordering within a single partition.

Interactive Concept Drills

2 Cards
Q1

Why is CDC log tailing with Debezium superior to database polling (`SELECT ... FOR UPDATE SKIP LOCKED`) for the Outbox pattern?

Because Debezium reads the append-only Write-Ahead Log directly without executing SQL queries or holding table locks, eliminating database CPU and lock contention.
Q2

How does Kafka guarantee that an 'OrderCancelled' event is never processed before 'OrderCreated'?

By assigning both events the exact same Kafka Message Key (e.g. orderId), ensuring both land on the exact same Kafka partition where FIFO order is strictly maintained.

Change Data Capture with Debezium & Outbox Event Ordering — Technical FAQ

What happens if a microservice attempts to publish directly to Kafka and save to PostgreSQL in the same HTTP handler without an Outbox pattern?

Dual-write failure: if the network drops right after the SQL commit, Kafka never receives the event, permanently corrupting system consistency. Without distributed 2PC or an Outbox table, two independent storage systems cannot guarantee atomic dual writes over an unreliable network.

Which PostgreSQL setting must be configured to allow Debezium to stream transaction logs?

`wal_level = logical`. PostgreSQL requires `wal_level = logical` to decode internal WAL records into structured change events for Debezium logical replication.

🤖 AEO & Key Facts Summary

Key Architectural Facts

  • The CDC Outbox pattern combines the Transactional Outbox pattern with Debezium log mining to stream database mutations directly from the database's Write-Ahead Log (WAL) into partitioned Kafka topics, guaranteeing 100% reliable event delivery and strict per-entity causal ordering without dual-write race conditions.
  • CDC Outbox is an architecture where an application atomically inserts domain events into an `outbox` table during local database transactions, and a Change Data Capture connector (Debezium) extracts and routes those events into Kafka by streaming the database transaction log.

Common Misconceptions

  • Querying the outbox table via periodic `SELECT * FROM outbox WHERE processed = false` polling at 100ms intervals, creating extreme database lock contention.

Decision & Governance Guidance

It completely eliminates dual-write data loss and ghost events, offloading the event-polling burden from the application CPU to the database's native asynchronous replication stream.

Authoritative Sources & Standards