Designing a CDC Pipeline: Change Data Capture from Binlog to Downstream
Every data team eventually needs the same thing: the OLTP database's changes, delivered elsewhere — to the search index, the cache, the warehouse, the fraud model. The obvious approach, dual writes ("write to Postgres and publish to Kafka"), is a correctness trap: the two writes aren't atomic, so crashes and races leave the copies permanently diverged, and no retry policy fixes a fork. Change data capture inverts the approach: stop announcing your changes — let the pipeline read the database's own write-ahead log, the one place where the truth is already totally ordered and durable.
The capture layer: tailing the log
Every serious database exposes its replication stream: MySQL binlog, Postgres logical decoding (WAL → row events via a replication slot), MongoDB change streams. A CDC connector (Debezium being the reference design) poses as a replica, decodes row-level events — INSERT/UPDATE/DELETE with before/after images — and publishes them, typically one Kafka topic per table, partitioned by primary key so each row's history stays ordered:
{op: "u", before: {id: 42, status: "pending"},
after: {id: 42, status: "paid"},
source: {lsn: 0x1A2B3C, table: "orders"}, ts_ms: ...}
Two properties fall out for free, and they're the whole reason to do it this way: nothing is missed (the log is the database's own definition of what happened — triggers, bulk updates, and out-of-band scripts included, which application-level events never reliably capture), and order per key is the commit order. The connector tracks its position (LSN/GTID) as a consumer offset; crash → resume from the last recorded position → at-least-once delivery of an idempotent-by-nature stream (row events carry full state, so downstream upserts absorb replays).
The hard part everyone skips: the initial snapshot
A new consumer needs the current table contents plus all changes after — and the table is being written while you copy it. The naive "dump, then stream" loses or duplicates the writes that happened mid-dump unless the handoff is exact. The clean design (DBLog/Debezium's watermark approach): interleave chunked snapshot reads with the live stream, using watermarks to deduplicate — mark position, read chunk, mark again; any streamed event for a key inside the chunk window supersedes the snapshot row. Result: a consistent snapshot without locking the source, resumable per chunk. In an interview, distinguishing "bootstrap" from "tail" — and knowing the bootstrap is the hard 20% — is what separates people who've operated CDC from people who've read about it.
Schema evolution: the operational 80%
ALTER TABLE happens; the pipeline must not corrupt or halt. DDL flows through the same log; the connector maintains an internal schema history and stamps every event with its schema version, while a schema registry enforces compatibility rules downstream (backward-compatible changes — add nullable column — flow through; breaking changes — drop/retype a column — get stopped at the source and force a coordinated migration: new topic, dual-publish window, consumer cutover). The registry is the data-contract enforcement point: without it, one hasty ALTER at 2am becomes silent nulls in the warehouse and a fraud model scoring garbage. Unglamorous, decisive.
Delivery semantics and the transformation tier
Downstream consumers see at-least-once, per-key-ordered events. The standard consumption patterns, each one sentence: cache invalidation — evict/upsert on event (TTL as backstop); search indexing — upsert documents, with a version check (event.lsn > doc.lsn) making replays harmless; warehouse — append raw events, then merge into current-state tables on a schedule (dbt-style), keeping the append log as the audit trail; event-carried state transfer — other services materialize local replicas of the table they keep asking you for. For consumers wanting transactions rather than rows, the log also carries transaction boundaries — buffering a transaction's events until its commit marker restores atomicity that per-row topics lose (and is exactly what feeding a read model demands).
Two sharp edges to name before the interviewer does. Resharding the source: key-partitioned topics preserve per-key order only while the partition count is fixed — plan topic partitioning up front or accept an ordering epoch on split. The outbox alternative: when what you want downstream is a domain event ("OrderPlaced") rather than row diffs, CDC on an outbox table gives you both worlds — the app writes the event in the same transaction as the data, CDC ships it exactly-once-ish; that's the transactional outbox pattern with CDC as its delivery mechanism, and choosing between "row CDC" and "outbox CDC" is a modeling decision, not a technology one.
| Interview probe | Answer sketch |
|---|---|
| Why not triggers writing to a queue table? | Triggers tax every write, are per-table toil, and the queue table becomes its own contention point; log tailing is invisible to the workload |
| Replication slot fills the disk? | The classic Postgres incident: a stalled consumer pins WAL forever — monitor slot lag, alert, and have a drop-and-resnapshot playbook |
| Exactly-once into the warehouse? | At-least-once + idempotent merge (dedupe on LSN/PK) — same answer as every exactly-once question, and it's fine |
| Multi-region source failover? | GTID-based positions survive primary failover; without them, CDC must re-anchor — say "position portability" and you've said the risk |
The durable principle: the write-ahead log is the database's own event stream — capture it instead of duplicating it. Dual writes ask two systems to agree; CDC asks one system to remember, and it already does.
Keep reading
Designing Chat Presence: Online, Away, and the Lie of Instant Status
Heartbeat vs subscriptions, fan-out of presence, privacy, and why a boolean online flag does not survive a million concurrent sockets.
Consistency Models Beyond CAP: Linearizability, Causal, and Session Guarantees
'Strong vs eventual' is a cartoon. The real spectrum — linearizable, sequential, causal, session guarantees — and how to pick per operation, not per system.
Designing an API Gateway: The Front Door as a System
Authentication, routing, rate limits, transformations, and a plugin chain — in a tier that must add ~1ms and never be the outage. Envoy/Kong architecture from scratch.
Designing a Content Moderation Pipeline: Trust and Safety at Platform Scale
A billion uploads a day, a legal requirement to act in hours, and irreversible mistakes in both directions: hash matching, classifier tiers, human review queues, and appeals.
Designing a Distributed Rate Limiter: Local Buckets, Global Truth
One user, twenty gateway nodes, one limit of 100 req/s. Where does the counter live? Redis+Lua atomicity, the local-cache compromise, and failing open vs closed.
Designing a Durable Workflow Engine: How Temporal Makes Code Survive Crashes
Event-sourced workflow state, deterministic replay, task queues with sticky caches, and why 'just write normal code' is the hardest API promise in infrastructure.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.