skip to content

Change Data Capture (CDC)

How committed changes in an operational database become a stream that other systems can consume, without asking the application to publish anything. CDC is the standard answer to "how do you keep the warehouse and the search index in sync with production?", so interviewers probe both the capture mechanism and the guarantees it gives you downstream.

on this pageshow

explore

questions

page 1 of 2

What is the difference between log-based and query-based change data capture?

level: juniorimportance: must knowfreq 82%

answer

  1. two ways: ask the table, or read the log
  2. one sees state, the other sees history
  3. polling cannot report what no longer exists
  4. the log already exists for replicas
  5. cost moves from query load to operations

basics

~20 s

Log-based capture reads the database's own replication log, so it sees every committed insert, update and delete in commit order. Query-based capture repeatedly polls the tables, so it only sees rows that still exist at poll time.

solid answer

~50 s

Query-based capture runs a scheduled query such as `SELECT * FROM orders WHERE updated_at > :last_seen` and remembers the highest marker it saw. It needs nothing from the database but a read-only login and a suitable column, so it works against almost any source — but it reads *current state*, not history: hard deletes never come back in a query, several updates between polls collapse into the newest version, and every poll costs the source a scan. Log-based capture attaches to the sequential change log the engine already writes for replication — the MySQL binary log, a Postgres logical replication slot, the SQL Server transaction log — and emits one event per committed change, with an operation type, before/after images and a log position. It sees deletes, sees every intermediate version, and adds almost no query load. Its costs are operational: elevated privileges, source configuration, and a log position that must keep advancing or the server retains log files.

code

sql · 5 lines
sql
-- query-based: state at poll time, filtered by a marker column
SELECT order_id, status, total, updated_at
FROM orders
WHERE updated_at > :last_seen
ORDER BY updated_at;

go deeper

for a junior

Be able to state the fork in one breath: polling asks the table what looks new, log-based capture reads the database's replication log. Then name the headline consequence — polling cannot see a deleted row.

for a middle

Explain the mechanics: which column a poll filters on, what a log record contains (operation, before, after, position), and why intermediate updates collapse under polling but not under log reading.

for a senior

Show that you have paid the log-based bill: privileges and source settings, a consumer position that must keep advancing, retained log files, and the fact that the capture stream is only as correct as the sink applying it.

for a principal

Own the choice as a platform decision — which sources can even support log-based capture, what freshness and delete-accuracy you are prepared to promise per source, and who carries the operational burden of the slots and log positions.

## The problem both approaches solve Something downstream — a warehouse, a search index, a cache, another service — needs to know when rows in a source database change. The source database is the authority; the only question is how you find out. There are exactly two families of answer: ask the source repeatedly what looks new (query-based, or polling), or read the log the source already writes as it commits (log-based). ## Query-based capture Query-based capture runs a scheduled query against the source tables and keeps a marker of how far it got. The usual marker is a monotonic column — an `updated_at` timestamp, a `version` counter, or an auto-increment id for append-only tables: ```sql SELECT * FROM orders WHERE updated_at > :last_seen ORDER BY updated_at; ``` The extractor stores the highest value it saw and asks again on the next tick. Nothing special is required of the database: a read-only login and a suitable column. That is why polling works against a managed service you cannot reconfigure, a read replica, a view, or a vendor system that only exposes a REST endpoint. ## What query-based capture structurally cannot see Polling reads *state*, not *history*, and three consequences fall straight out of that: - **Hard deletes are invisible.** A row that has been `DELETE`d matches no query, so nothing ever tells the extractor it is gone; the downstream copy keeps it forever. Only a soft delete — a `deleted_at` flag the application sets instead of deleting — makes deletes pollable, and that is an application design decision the pipeline does not control. - **Intermediate versions collapse.** If a row is updated five times between two polls, the poll returns the fifth version. For a warehouse fact that is often fine; for an audit trail or anything that reacts to a specific transition ("the order passed through CANCELLED"), the missing states are lost data. - **The marker column must be trustworthy.** A write path that forgets to touch `updated_at`, or a bulk update that stamps it from a stale application clock, produces rows the extractor never picks up. ## Log-based capture Every durable relational engine already writes a sequential record of committed changes so replicas can follow it: the write-ahead log in Postgres, surfaced through logical decoding on a replication slot; the binary log in MySQL; the transaction log in SQL Server; redo and archive logs in Oracle. Log-based CDC registers as a consumer of that stream and turns each record into a change event — operation type, the row before, the row after, and a position (an LSN, a GTID, an SCN) identifying where in the log it came from. Because it reads the log, it sees what the database actually committed: deletes appear as delete events, every intermediate update appears as its own event, and events arrive in commit order. It also imposes almost no query load — the log is written anyway, and reading it sequentially does not scan tables, take table locks, or evict the application's working set from the buffer pool. ## What log-based capture costs The cost does not disappear; it moves from query load to operations. - **Source configuration and privileges.** Logical WAL level and a replication role in Postgres; row-based binary logging with full row images in MySQL; supplemental logging in Oracle. Some vendor-controlled databases grant none of it. - **Retained log.** A consumer position that stops advancing forces the server to keep log files it would otherwise recycle. An abandoned Postgres replication slot filling the primary's disk is the classic CDC outage. - **Before-image completeness.** What a delete or update event carries about the *old* row is decided by source settings, not by the capture tool. - **Schema and type fidelity.** The log speaks the engine's physical types and DDL, so the pipeline must keep interpreting them as the source schema evolves. ## Latency and load Polling's latency floor is the poll interval, and shortening it multiplies scan cost: a one-minute interval scans the table 1,440 times a day whether or not anything changed. Log-based capture is continuous, so latency is bounded by how fast the reader drains the log — typically sub-second — and its cost does not grow when you want fresher data. ## When query-based is still the right call If the source is a third party you cannot reconfigure, if the table is append-only and small, if hourly freshness is enough, or if nobody will own a replication slot at 3 a.m., a polling extract is the honest engineering choice. The failure to avoid is choosing polling and then quietly promising delete-accurate replication downstream.

  • At one-minute freshness, which approach costs the source database more, and why?
    Polling, by a wide margin. Each tick runs a filtered scan against live tables — 1,440 scans a day per table whether or not anything changed — competing with the application for I/O and buffer pool. Log-based capture reads a sequential log the engine writes anyway, so halving latency does not double its cost.
  • What must a source database expose before log-based capture is even possible?
    A readable, row-level change log and the privilege to consume it: logical-level WAL plus a replication role in Postgres, row-based binary logging in MySQL, supplemental logging in Oracle. Managed and vendor-hosted databases frequently withhold one of these, which is the usual reason a team ends up polling.
  • Does log-based capture guarantee the downstream sink is never wrong?
    No. It guarantees the *stream* contains every committed change in commit order; correctness downstream still depends on the sink applying events idempotently, on the capture position surviving restarts, and on the initial state being loaded consistently before streaming begins.

Polling is walking the shop's shelves every hour and inferring what changed; log-based capture is reading the till receipts as they print.

saying these in an interview costs you the question

  • Claiming polling catches deletes if you poll often enough
  • Saying log-based CDC has no cost to the source
  • Treating a timestamp column as guaranteed monotonic and always set
  • Describing log-based capture as running queries against the tables
  • Assuming every managed database allows a replication slot

context

open as a page

Why does a log-based CDC pipeline take an initial snapshot before it starts streaming the log?

level: juniorimportance: must knowfreq 72%

basics

~10 s

A transaction log records only changes, and only recent ones. Rows untouched since the log's retention window began appear nowhere in it, so the pipeline must read the table once to establish a baseline.

open as a page

In Debezium, how do the snapshot.mode values initial and initial_only differ?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Both read the full contents of the captured tables on first start. With initial the connector then switches to streaming the transaction log and follows it forever; with initial_only it shuts down once the snapshot finishes and never streams.

open as a page

Why does polling a source table's updated_at column miss deletes and intermediate row versions?

level: middleimportance: must knowfreq 74%

basics

~20 s

A poll returns rows that exist now and match its filter. A deleted row matches nothing, so it is never reported; several updates between two polls return only the newest version. Polling observes current state, never history.

open as a page

Why must a sink applying CDC change events use idempotent upserts rather than replaying source statements?

level: middleimportance: must knowfreq 70%

basics

~20 s

A CDC pipeline redelivers events after any restart, so a replayed insert collides on the key and a replayed relative update double-counts. Writing each event's after-image as an upsert keyed on the primary key makes redelivery a no-op.

open as a page

In a CDC pipeline, what guarantees that two updates to the same row reach the sink in source order?

level: middleimportance: must knowfreq 62%

basics

~20 s

Ordering is per key, not global. One capture process reads the log in commit order, events are keyed by the row's primary key so a row's whole history travels one ordered route, and one worker applies that key at a time.

open as a page

How does a CDC connector hand over from its initial snapshot to log streaming without a gap or a duplicate?

level: middleimportance: must knowfreq 76%

basics

~20 s

The connector pins a log position before or at the moment it takes its consistent read of the tables, then resumes streaming from exactly that position. Gaps are unacceptable; overlap is fine because sinks upsert by primary key.

open as a page

How do you restrict which tables and columns a Debezium connector emits change events for?

level: middleimportance: must knowfreq 66%

basics

~20 s

Set table.include.list or table.exclude.list to regular expressions matching fully-qualified table names, and column.include.list, column.exclude.list or a column mask for fields. Debezium matches the whole identifier, and the include and exclude forms of one option are mutually exclusive.

open as a page

In Debezium, what does the required topic.prefix connector property control?

level: middleimportance: must knowfreq 62%

basics

~20 s

topic.prefix is the connector's logical source name. Debezium puts it in front of every change topic, names the schema-change, heartbeat and transaction topics with it, and records it in stored state, so it must be unique per connector.

open as a page

What does Debezium's ExtractNewRecordState SMT do to a change event, and how does it treat deletes?

level: middleimportance: must knowfreq 68%

basics

~20 s

Debezium's ExtractNewRecordState SMT flattens the change-event envelope down to the after row image, so a sink sees an ordinary record instead of before/after/op. Deletes arrive with a null after image and are dropped by default.

open as a page

In Debezium, how do you trigger an incremental snapshot of one table without restarting the connector?

level: middleimportance: must knowfreq 58%

basics

~20 s

Send an execute-snapshot signal. Configure signal.data.collection to point at a signalling table with id, type and data columns, then insert a row whose type is execute-snapshot and whose data names the tables to re-read. The connector backfills them in chunks while streaming continues.

open as a page

Why does a Debezium MySQL connector keep its own schema history topic, and what breaks if that topic is lost?

level: seniorimportance: must knowfreq 57%

basics

~20 s

MySQL's binlog carries row values without column names, so the connector stores every captured DDL statement in a private schema history topic to decode rows at any log position. Lose that topic and the connector cannot start until its history is rebuilt.

open as a page

In a CDC feed, what should a sink do with a delete event?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Apply it by key: either remove the row, or mark it deleted with a flag and a timestamp so history survives. Ignoring delete events leaves rows in the target that no longer exist in the source, and counts drift upward forever.

open as a page

Which Debezium connector class do you configure for a MySQL, PostgreSQL, MongoDB or SQL Server source?

level: juniorimportance: should knowfreq 50%

basics

~10 s

Debezium ships a separate connector class per source: MySqlConnector, PostgresConnector, MongoDbConnector and SqlServerConnector, all under io.debezium.connector. Each reads that engine's own change log and has its own database-side prerequisites.

open as a page

In Postgres logical replication, what does REPLICA IDENTITY control in a CDC stream's change events?

level: middleimportance: should knowfreq 52%

basics

~20 s

It decides what the old row image in an UPDATE or DELETE record contains: only the primary-key columns by default, a chosen unique index, the whole previous row with FULL, or nothing. Consumers see exactly that much and no more.

open as a page

How can a CDC snapshot read a consistent copy of a source table without locking it for the whole scan?

level: middleimportance: should knowfreq 54%

basics

~20 s

Open the scan inside a repeatable-read transaction whose read view is pinned to the log position, so it sees one frozen instant for hours while writers proceed untouched. Any lock is needed only for the moment of pinning, not for the scan.

open as a page

Which outbox table columns does Debezium's EventRouter SMT read, and how does it pick the topic and key?

level: middleimportance: should knowfreq 52%

basics

~20 s

Debezium's EventRouter reads an outbox row's id, aggregate type, aggregate id, event type and payload columns. The aggregate type selects the destination topic, the aggregate id becomes the message key, and the payload becomes the message value.

open as a page

In a Debezium MySQL connector, how does snapshot.mode=never differ from schema_only?

level: middleimportance: should knowfreq 48%

basics

~20 s

Both skip copying existing rows. schema_only first reads the current table definitions into the connector's schema history and starts streaming from the end of the binlog. never captures no schema at all, so the connector must rebuild it by replaying DDL from the binlog.

open as a page

Why does a logical replication slot that no CDC consumer reads fill the source primary's disk?

level: seniorimportance: should knowfreq 58%

basics

~20 s

A slot records the oldest log position its consumer still needs, and the server refuses to recycle write-ahead log files at or after that position. If nothing advances the slot, the retained log grows without bound until the disk is full.

open as a page

How do you stop a replayed CDC event from overwriting a newer value already in the sink?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Store the change's source log position on each sink row and make the merge conditional: apply the incoming event only when its position is greater than the one already stored. An older or replayed image is then ignored rather than written.

open as a page

Why can a CDC consumer see an order row before its order_items, and what fixes it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Capture emits one event per row change, so a single source transaction that wrote both tables becomes independent events on independent routes. Atomicity stops at the source; the consumer sees an intermediate state that converges seconds later.

open as a page

A CDC initial snapshot of a 2 TB table has run eight hours and the source's disk is filling — what is happening?

level: seniorimportance: should knowfreq 58%

basics

~20 s

Streaming has not started, so every log segment from the pinned position onward must be retained, and the snapshot's open read view blocks reclamation of superseded rows. Both grow with snapshot duration, and the source runs out of space before the scan ends.

open as a page

Why would you set heartbeat.interval.ms on a Debezium Postgres connector capturing a low-traffic table?

level: seniorimportance: should knowfreq 52%

basics

~20 s

With no writes to captured tables Debezium emits nothing, commits no new offset, and the PostgreSQL replication slot never advances, so write-ahead log accumulates until the disk fills. Heartbeats emit periodic events so offsets and the slot keep moving.

open as a page

Two Debezium MySQL connectors share the same database.server.id — what happens on the source?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Each Debezium MySQL connector registers as a replication client identified by database.server.id. When two share an id, MySQL drops one of them; both reconnect, kick each other off again, and neither keeps up with the binlog.

open as a page

A captured table gains a nullable column; what do Debezium's Avro consumers and the schema registry see?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Debezium emits a DDL change event and then change events whose value schema carries the new optional field, so a new schema version is registered. Adding a nullable column is compatible; dropping or retyping a column is what stops the pipeline.

open as a page

Your Debezium initial snapshot has run eight hours and the source's replication slot keeps growing — what now?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The connector is snapshotting, so it is not consuming the log yet, and the slot retains every change since the snapshot began until the source disk fills. Triage disk headroom first, then restart with a mode that streams immediately and backfill the tables with incremental snapshots.

open as a page

In Debezium, what do the snapshot-window-open and snapshot-window-close signals do?

level: seniorimportance: should knowfreq 36%

basics

~20 s

They mark the boundaries of one incremental-snapshot chunk in the transaction log. Debezium buffers the chunk's rows, drops from that buffer any key it sees change between the two markers, and emits what remains — so a live change always wins over the older snapshot read.

open as a page

How would you capture changes from a vendor database where you cannot enable log-based CDC?

level: principalimportance: should knowfreq 36%

basics

~20 s

Work down a ladder: negotiate log or replica access, then a vendor-supplied event feed, then triggers if you may create objects, then polling plus periodic key reconciliation. Whatever you land on, publish what it does and does not guarantee about deletes and freshness.

open as a page

How would you plan the initial CDC snapshot of a 4 TB production database that cannot absorb extra load?

level: principalimportance: should knowfreq 34%

basics

~20 s

Decide four things before touching the source: where the baseline comes from, where it is read, how it is paced, and whether the pinned log position survives the elapsed time. Duration against log retention is the constraint that governs all of them.

open as a page

How would you evolve the payload schemas of Debezium outbox events without breaking downstream consumers?

level: principalimportance: should knowfreq 36%

basics

~20 s

Treat the outbox payload as a published contract: additive, optional-only changes by default; an explicit event version for breaking ones, published alongside the old until consumers migrate; and a subject naming strategy that lets several event schemas share one routed topic.

open as a page

showing 1–30 of 36