Change Data Capture in Practice
also called CDC
Turning a database's replication log into an event stream, so downstream systems learn about changes without the application publishing them.
Definition
CDC tails the database's write-ahead or binary log and emits each committed row change as an event. Because it reads the log the database already writes for replication, it captures every change — including those made by batch jobs, admin tools and manual fixes that application-level publishing would miss.
Why it matters
Two properties make it valuable:
Completeness. Application-level event publishing captures what the application remembers to publish. CDC captures what actually happened. In a system that has been alive for five years, the difference is large.
No change to the writer. A downstream consumer can be added without touching the source application at all, which matters enormously when the source is a legacy system nobody wants to modify — which is why CDC is a standard tool in strangler migrations.
Industry example
Netflix's approach to change capture illustrates the operational subtlety. The naive implementation takes a lock or a snapshot to establish a consistent starting point before tailing the log, which is disruptive on a busy production database. Their published work on log-based capture emphasises performing the initial state dump without locking, interleaved with the ongoing log stream, so that bootstrapping a new consumer does not degrade the source.
That detail matters because bootstrapping is not a one-off: every new consumer, every reprocessing after a bug, and every schema change may require it. A CDC design that is only viable at initial setup is a design that discourages exactly the flexibility CDC exists to provide.
Implementation patterns
- Log-based, not query-based. Polling for
updated_at > xmisses deletes, misses changes within the polling interval, and requires a column that some tables do not have. - Emit before and after images where the consumer needs to know what changed, not just the new state.
- Preserve per-row ordering by partitioning downstream on the primary key.
- Handle schema changes explicitly. A column added upstream flows downstream; a column dropped breaks consumers. Compatibility rules and a registry are required, not optional.
- Snapshot plus stream for bootstrapping, with a defined handover point.
Failure scenarios
- CDC used to publish domain events. Row changes are not business facts.
ordersrow updated is notOrderCancelled, and consumers forced to infer intent from column diffs will get it wrong. CDC is excellent for data movement and poor as a domain-event mechanism; where you need semantic events, use an outbox table and capture that. - The internal schema becomes a public contract. Every consumer now depends on your column names, so you can no longer refactor. This is the most common long-term regret.
- Replication slot not consumed, so the database retains log segments until the disk fills. This takes down the source database, which is a spectacular way for an analytics pipeline to cause a production outage.
- Deletes handled as absence rather than as tombstone events, so downstream keeps records forever.
Trade-offs
Bought: complete capture, no writer changes, low latency, and an excellent migration tool. Sold: coupling to the physical schema, a new operational dependency on the database's log, and a strong temptation to treat storage-level changes as domain events.
Interview question
"When would you use CDC rather than having the application publish events, and what do you give up by doing so?"