What Is CDC? Change Data Capture and Log-Based CDC Explained

Change data capture (CDC) detects every row change committed to a source database, deletes included, and delivers each one downstream as an event. Log-based CDC does it by reading the database's transaction log, so it sees every change in commit order, including deletes, without querying the source tables.

Last updated: Proudly published by: Jeff Wahl9 min read

What is change data capture (CDC)?

Change data capture is the practice of detecting each row change committed to a source database, deletes included, and delivering it downstream as an event, so other systems can stay in sync without copying whole tables again. Log-based CDC does the detecting by reading the database's own transaction log, which already records every committed change in order.

The alternative to capturing changes is reloading: a nightly job copies the full table, or an hourly job selects rows whose updated_at moved. Both work until the table is large, the window is short or someone deletes a row. CDC replaces that with a stream where cost scales with the number of changes, not the size of the table, and where latency is seconds instead of the length of a batch window.

Typical uses are replicating an operational database into a warehouse or lakehouse, keeping a search index or cache fresh, and publishing row changes for other services to react to.

Prepare for the interview
01 / Open invite
02min.

Know Change data capture the way the interviewer who asks it knows it.

a Change data capture query, the same shape a screen would give you.
The diff against expected. Where ties broke. What you missed.
sandbox
1source → bronze → silver → gold
2 ingest : CDC + Kafka
3 transform : dbt + Airflow
4 serve : Snowflake
5
Execute your solution0.4s avg.
TikTokInterview question
Solve a Change data capture problem

How log-based CDC works

A relational database writes every committed change to a transaction log before it touches the data files, so it can recover after a crash and feed replicas. Log-based CDC is a connector that reads that same log, decodes each record into a row-level event and publishes it to a stream. The source tables are never queried, which is why the load on the database stays low.

Flow from the source database to its transaction log, to a connector that reads the log, to a change stream that feeds a warehouse, a search index and a cacheFlow from the source database to its transaction log, to a connector that reads the log, to a change stream that feeds a warehouse, a search index and a cache

Each event carries a position in the log, such as a PostgreSQL log sequence number (LSN). The connector stores the last position it handled and resumes from there after a restart. Because the log is ordered by commit, a consumer sees changes in the order they happened, and one connector can feed a warehouse and a search index from the same stream.

How PostgreSQL, MySQL and SQL Server expose their logs

PostgreSQL implements logical decoding, which decodes the write-ahead log, a record of storage-level changes, into a stream of row changes. It requires wal_level = logical, which the documentation says increases WAL volume, especially with many tables on REPLICA IDENTITY FULL and heavy updates and deletes. A consumer reads through a replication slot: a stream that persists independently of the connection, is crash-safe and emits each change once in normal operation.

MySQL's binary log records data changes for replication and point-in-time recovery. Row capture needs binlog_format = ROW, the default in MySQL 8.4, and the default binlog_row_image = FULL logs every column of the before and after image. A delete logs only the before image, an insert only the after image and an update both.

SQL Server takes a different route: CDC is enabled per database with sys.sp_cdc_enable_db and per table with sys.sp_cdc_enable_table, and a SQL Server Agent capture job reads the transaction log into change tables that mirror the source columns. By default the job processes up to 1,000 transactions per scan cycle with a 5 second wait, and a cleanup job keeps 4,320 minutes, or 3 days, of change rows. The Microsoft documentation notes a built-in latency between a commit and its row appearing in the change table.

Log-based CDC setup on 3 databases

PostgreSQL
  • Log it readsWrite-ahead log
  • What to enablewal_level = logical
  • Position markerLSN
  • Change lands inReplication slot
MySQL
  • Log it readsBinary log
  • What to enablebinlog_format = ROW
  • Position markerFile and offset
  • Change lands inBinlog stream
SQL Server
  • Log it readsTransaction log
  • What to enablesp_cdc_enable_table
  • Position markerLSN
  • Change lands inChange table

Log-based vs query-based vs trigger-based CDC

Query-based CDC runs a SELECT on a schedule, usually filtering on an updated_at or version column. Take order 1042 in orders, polled every 5 minutes. Between 2 polls it is committed as paid, shipped, delivered: 3 changes. The next poll sees only the row as it stands, delivered, so 2 of 3 changes never reach the downstream system, and a log reader records all 3.

A hard delete is worse: the row is gone, so the query has nothing to return and the delete is invisible unless the source marks rows deleted instead of removing them. Polling also adds a query to the production database on every cycle, and needs an indexed change column that every writer keeps correct.

2 timelines of 5 minutes: the log reader records 3 commits to an order, paid at 0:40, shipped at 1:50 and delivered at 3:10; a poll every 5 minutes misses the first 2 and sees only delivered at 5:002 timelines of 5 minutes: the log reader records 3 commits to an order, paid at 0:40, shipped at 1:50 and delivered at 3:10; a poll every 5 minutes misses the first 2 and sees only delivered at 5:00

Trigger-based CDC puts a database trigger on each table that writes every change to an audit table, which a pipeline then reads. It sees deletes and intermediate states, but the trigger runs inside every write transaction and slows it, and each new table needs a trigger kept in step with its schema. Reach for it only when the database exposes no readable log. For production replication, log-based capture is the default choice: it sees all 3 kinds of change, orders them by commit and costs the writers nothing.

What a CDC change event contains

Debezium, the most widely used open-source log-based connector, publishes each change as an event whose value holds before, after, source and op, plus ts_ms. The op field is c for a create, u for an update, d for a delete and r for a row read during a snapshot. before is null on a create, and after is null on a delete because the row no longer exists.

The source block names the source table by database and schema, and records the log position, which is the field to use for ordering and deduplication. After a delete event, Debezium emits a tombstone: the same key with an empty value, which lets a compacted Kafka topic drop the key's history.

Whether before is filled depends on the source. On PostgreSQL, REPLICA IDENTITY DEFAULT gives the previous primary key values on updates and deletes, FULL gives the previous values of every column, and NOTHING gives none. A table with no primary key and the default setting produces only create events. If a downstream job needs to know what a value was, set the identity to FULL or index that table on the columns it needs, and accept the extra WAL.

An update event for order 1042

{
  "before": { "id": 1042, "status": "paid" },
  "after":  { "id": 1042, "status": "shipped" },
  "source": {
    "connector": "postgresql",
    "db": "shop",
    "schema": "public",
    "table": "orders",
    "lsn": 24023128,
    "snapshot": "false"
  },
  "op": "u",
  "ts_ms": 1767225600000
}

The shape follows Debezium's PostgreSQL connector, trimmed to the fields that matter here

How CDC takes the first copy: snapshots and backfills

The log holds recent changes, not history, because databases recycle old log segments. A new pipeline therefore starts with a snapshot, a consistent read of the existing rows, then switches to streaming from the log position recorded at the start of the snapshot. Debezium tags snapshot rows with op = r, and its snapshot.mode setting decides when to snapshot: initial is the default and snapshots only when no offsets are stored, when_needed also re-snapshots if the recorded log position is gone from the server, and no_data skips the snapshot and streams only.

A full snapshot of a large table can run for hours, and changes keep arriving the whole time. Netflix's DBLog framework, described in a 2020 paper by Andreakis and Papapanagiotou, avoids blocking the log: it reads the table in chunks and uses watermarks to interleave those chunk reads with log events, so log processing does not stall and no locks are taken. The same mechanism lets an engineer re-snapshot one table later, for a backfill, without stopping the stream.

Delivery guarantees: at least once, so make the consumer idempotent

Plan for duplicates. The PostgreSQL documentation says a slot's position is persisted only at checkpoint, so after a crash it can return to an earlier LSN and send recent changes again, and that logical decoding clients are responsible for avoiding ill effects from handling the same message more than once. Debezium's FAQ puts it the same way: in normal operation each event is seen once, and after a failure a consuming application may see an event at least once.

The fix is an idempotent apply. Key every event by the source primary key, carry the log position, and merge into the target so that replaying an event leaves the same result. An idempotent pipeline applies an event only if its position is newer than the one stored for that key. Ordering holds per key as long as one key always goes to one partition, which is why CDC topics are partitioned by primary key.

What goes wrong with log-based CDC in production

The most common incident is a stalled consumer. PostgreSQL replication slots know nothing about their consumers and keep required WAL and catalog rows even when nothing is connected, so a dead connector fills the disk, and the documentation warns that in extreme cases the database can shut down to prevent transaction ID wraparound. Alert on slot lag in bytes, and drop slots that are no longer needed. SQL Server behaves alike: with CDC enabled, the log truncation point will not advance until the capture process has gathered the changes, even under simple recovery.

Schema changes come second. A connector decoding a log has to survive an added or dropped column. SQL Server keeps a fixed column structure in the change table, ignores new columns until you create a second capture instance, and supplies nulls for dropped ones. Decide before launch how added columns reach the warehouse and who is told when the contract changes.

Third is the hidden cost on the source: wal_level = logical raises WAL volume, and large transactions arrive as one burst when they commit. Permissions and failover need planning too. Treat the connector like a production service with its own lag and error alerts.

Applying a change stream to a warehouse table

A change stream is a log of events, not a copy of the table, so something has to apply it. The usual pattern lands raw events in an append-only table, then runs a scheduled MERGE that keeps the newest event per primary key and upserts it into the target table; rows whose latest event is d are deleted instead.

If an analyst needs to know which address a customer had when an order shipped, the same events feed a slowly changing dimension type 2 table: each update closes the open row and opens a new one, and the event timestamp supplies the validity dates. Event-by-event capture is what makes this possible, which a periodic snapshot or a poll cannot offer.

CDC in data engineering interviews

CDC comes up in pipeline design rounds whenever a prompt says to replicate an operational database into a warehouse with low latency and no load on production. A strong answer names log-based capture and says why polling loses intermediate states and deletes, then covers the first snapshot and ordering per key, and explains how merges stay idempotent and what happens when the consumer falls behind.

Practice that design under time on data pipeline interview questions and data pipeline practice problems. For questions asked about CDC specifically, see CDC pipeline interview questions, and for how the round is run, the system design round guide.

Change data capture FAQ

What is CDC in data engineering?+
CDC, short for change data capture, finds every row change committed to a source database, deletes included, and delivers each one downstream as an event. A warehouse table applies the events to stay in sync without reloading the whole table.
What is log-based CDC?+
Log-based CDC reads the database's own transaction log, such as the PostgreSQL write-ahead log, the MySQL binary log or the SQL Server transaction log, and turns each committed change into an event. It never queries the source tables, so it sees every change in commit order, including deletes.
What is the difference between log-based and query-based CDC?+
Query-based CDC runs a SELECT on a schedule and compares the result with the last run, so it sees only the state at each poll and cannot see hard deletes. Log-based CDC reads the commit log, so it records every intermediate change and every delete, with little extra load on the tables.
Does CDC guarantee exactly-once delivery?+
No. Log-based tools deliver at least once: after a crash a change can be sent again. PostgreSQL's documentation says logical decoding clients are responsible for avoiding ill effects from handling the same message more than once, so consumers should be idempotent, for example by merging on the primary key and the log position.
Which databases support log-based CDC?+
The common relational ones do: PostgreSQL through logical decoding with a replication slot, MySQL through its binary log in row format, and SQL Server through its capture job and change tables. Each needs a setting turned on and permissions granted before a connector can read changes.
What happens to the log if the CDC consumer stops?+
The database keeps it. A PostgreSQL replication slot prevents removal of the WAL it still needs, and in extreme cases the database can shut down to prevent transaction ID wraparound. SQL Server will not advance its log truncation point until the capture process has gathered the changes. Monitor slot lag and drop slots nobody uses.
Is CDC the same as ETL?+
No. ETL describes moving and reshaping data. CDC is a way of detecting what changed so that the move is incremental. A CDC stream usually feeds an ELT load that merges changes into warehouse tables.
02 / Why practice

The candidate who gets the offer

  1. 01

    Reading a solution is not the same as writing one

    Every engineer who has frozen on a query they had read a dozen times knows the gap. The only preparation that closes it is producing the answer yourself, under time, before the interview does it for you

  2. 02

    76% of hiring managers reject on the coding task, not the resume

    From HackerRank's 2024 Developer Skills Report. Candidates who look strong on paper still fail the live screen if they haven't done timed, executable practice

  3. 03

    System design comes down to the calls you defend out loud

    Ingestion, batch vs streaming, the bronze/silver/gold layers, idempotency, backfill and replay. Sketching the pipeline and naming the failure modes is the signal, not the boxes

Related guides