What it is
CDC reads the transaction log of a database (Postgres WAL, MySQL binlog, DynamoDB Streams) and turns each committed change into an event on a message bus. Consumers subscribe to that stream and derive whatever they need: invalidate cache keys, update Elasticsearch, land a row in the data warehouse.
The interview value is knowing the alternative — dual writes — and why it's dangerous. If your app writes to the DB and separately writes to Kafka, one of those writes will occasionally fail while the other succeeds. CDC removes that failure mode by driving the event from the same log the database uses for its own durability.
Every derived data store I have — cache, search index, analytics — hangs off CDC on the primary. That way the primary is the only place I write, and I never risk a dual-write drift.
The variants and when to pick each
Log-based CDC (WAL / binlog)
The best kind. A connector reads the database's replication log directly, emits every committed change as an event. Row-level, ordered per table, exactly the same source the DB's own replicas use — so it's as consistent as replication itself. Debezium, AWS DMS, and Postgres logical replication are the standard tools.
The events include before/after images for updates and a delete marker for deletes — enough to keep any downstream in sync without extra queries.
Trigger-based CDC
Database triggers on every table copy each change to a dedicated events table; a poller reads that table and publishes. Portable across engines that don't expose a log (or where you can't get replication permissions), and application-visible so debugging is easier.
The cost is that every write now writes twice — measurable overhead on hot tables, and triggers can cause surprising lock contention.
Transactional outbox
Not strictly CDC, but the pattern people reach for when they can't get to the WAL. The app writes to an events table in the same transaction as the state change; a shipper polls that table and publishes to Kafka. Same effect as CDC without the WAL access.
This is the workhorse pattern for microservices without a shared database platform team.
Snapshot + stream: bootstrapping a new consumer
A new consumer joining months into the game can't just start reading from the current log offset — it will miss the world's initial state. The standard bootstrap: consumer takes a consistent snapshot (SELECT * FROM table AS OF LSN X), then subscribes to the log from offset X and applies events forward. The two together give a complete replica.
Debezium handles this transparently with 'initial snapshot' mode; hand-rolled connectors need to implement the LSN-anchored snapshot manually.
Numbers worth memorising
| Replication lag under normal load | ~100 ms Postgres logical, ~50 ms MySQL binlog |
| Replication lag during heavy writes | seconds to minutes if consumers can't keep up |
| WAL retention | hours to days depending on config; can't fall behind by more |
| Events per second capacity | ~10k-50k per Debezium connector; parallelise per table |
| Bootstrap snapshot | table_size / throughput; minutes for GB, hours for TB |
Where the interviewer pushes
First push: 'why not just have the app write to both the database and Kafka?' Answer with the concrete failure: the write to Kafka happens after the DB commit, and the process crashes between them — the DB has the change, Kafka doesn't, downstreams silently drift. CDC eliminates this by design because the same log the DB uses for its own replication is the event source.
Second push: 'what happens if the consumer is down for a day?' The message bus buffers (Kafka retention is configured for this). If the outage exceeds retention, the consumer has to bootstrap from a snapshot again. Retention needs to be at least the longest tolerable consumer outage — usually a week, sometimes more for cold analytics jobs.