Lesson 3 · CDC foundations & the source
Snapshots
The WAL only holds what happens next — so how do the rows that already exist get into the stream? And how do you add a table without losing the slot?
Your win: explain why snapshots exist, distinguish initial from incremental snapshots, and describe the signal-table trick this repo uses to add a table to CDC without recreating the source connector.
The gap the WAL can't fill
A replication slot only streams changes from the moment it's created forward. But a table already has millions of existing rows — and the destination needs those too, not just future edits. Filling that gap is a snapshot: reading the current rows of a table and feeding them into the stream as if they'd just been written.1
Initial snapshot — the whole table, once
The simplest mode: on first connect, read the entire table, emit every row, then switch
to streaming the WAL. Controlled by snapshot.mode:
snapshot.mode = "never" // generated default: skip the initial snapshot, rely on incremental
snapshot.mode = "initial" // bob.json override: snapshot the whole table on first connect
Initial snapshots are simple but blunt — they read the whole table at once, which for a big table
is slow and heavy, and if the connector restarts mid-snapshot it may start over. That's why the
generated config defaults to never and leans on the smarter mode below.
Incremental snapshot — snapshot while streaming
The modern approach (Debezium's headline feature): snapshot existing rows while the WAL stream keeps running, in resumable chunks, triggered on demand. No stopping the stream, no starting over on restart.2
signal.data.collection = "public.dbz_signals" // the signal table
incremental.snapshot.chunk.size = "512" // rows per chunk
It's triggered by a signal: you INSERT a row into a special signaling table, and the running connector picks it up and starts chunking through the requested tables. In this repo the signal is written by a plain SQL INSERT:
func IncrementalSnapshot(ctx, db, signalTable string, data DataCollection) error {
time.Sleep(snapshotWaitDuration) // wait for the Debezium task to be ready
// wait until the replication slot is active (SELECT active FROM pg_replication_slots …)
sql := fmt.Sprintf("INSERT INTO %s VALUES ($1, $2, $3)", signalTable)
db.Exec(ctx, sql,
id, // a unique id (sourceID + ULID)
"execute-snapshot", // the signal type
data.String()) // {"data-collections": ["public.users", …]}
}
--sendIncrementalSnapshot flag (Lesson 8).
subscription.go has a NATS-JetStream-driven snapshot
handler that's entirely commented out (:108-142). The team started
with a NATS-message-triggered flow, then simplified to the direct SQL INSERT above.
So if you grep and find NATS snapshot code, know it's dead — the live path is
IncrementalSnapshot. (A good reminder to check what runs, not just what exists.)
Debezium snapshots & signalling
How ad-hoc and incremental snapshots work, the signaling data collection, and the watermarking that lets a snapshot run alongside the live stream.
→ debezium.io — Incremental Snapshots
→ debezium.io — Sending signals ·
in-repo internal/golibs/debezium/incremental_snapshot.go
Check yourself (from memory)
Q1. Why does CDC need a snapshot at all?
Q2. How is an incremental snapshot triggered here?
execute-snapshot and a data-collections payload. (The NATS flow was abandoned.)
Q3. Why add a new table via incremental snapshot instead of recreating the source connector?
snapshot.mode=initial): read the
whole table once on connect, then stream — simple but heavy, restarts from scratch. Generated
default is never. Incremental: snapshot existing rows while
streaming, in resumable chunks (chunk.size=512), triggered by a
signal: INSERT into public.dbz_signals a row (id,
'execute-snapshot', {"data-collections":[…]}) — debezium.IncrementalSnapshot
does this via direct SQL after the slot is active (the NATS flow is dead code). Killer
use: add a new table to CDC by adding it to the include list + firing an incremental
snapshot — without recreating the source connector (which would drop the slot). Wired to
hephaestus's --sendIncrementalSnapshot.2. Debezium — Incremental Snapshots, signalling. In-repo: internal/golibs/debezium/incremental_snapshot.go:16-72.