CDC Failure Modes and the Retention Deadline
A consumer falls behind. Whether that is an inconvenience or an unrecoverable data loss is decided entirely by whether the connector's position is still inside the source's retained log.
Who needs this, what one row is, and why the obvious build breaks
Every lesson starts from the consumer, because designing from the source outward is this domain's characteristic mistake.
The connector has been down since Friday and it is now Monday. Can you replay what you missed — and how would you find out before you promise someone that you can?
The on-call engineer at 02:00, who needs one number to know whether this is a lag incident or a data-loss incident; and every downstream consumer of the affected tables, whose data is either late or permanently short and who deserve to be told which (Data Incidents).
The unit here is the position: the connector's confirmed place in the source log, and the oldest position the source still retains. Every question in this lesson reduces to the distance between those two numbers and its sign.
Monitor the connector process. If it is running, capture is working; if it dies, restart it and it will pick up where it left off. This is true, it covers the common case, and it is what almost every CDC deployment starts with.
The connector is running and its position is not advancing. A long idle-in-transaction session upstream is holding the log open, so there is nothing to decode, no error to raise, and lag climbs behind a perfectly green process (Replication Lag: Reads That Are Correct and Stale).
- The connector is running and its position is not advancing. A long idle-in-transaction session upstream is holding the log open, so there is nothing to decode, no error to raise, and lag climbs behind a perfectly green process (Replication Lag: Reads That Are Correct and Stale).
- The connector is down over a weekend and its replication slot is holding log segments the source cannot recycle. The disk fills, and an analytics outage becomes a production database outage — the most severe failure this module contains.
- Someone drops the slot to free the disk. The source recycles the log, and every change during the outage ceases to exist anywhere. The pipeline restarts cleanly, reports itself healthy, and is permanently missing three days (Missing Rows).
- The connector restarts from its last committed offset and republishes everything since. An append-only downstream sink now holds duplicates for the entire replay window (Duplicate Rows).
- A bulk
UPDATEproduces millions of events. The broker absorbs them, the consumers do not, and lag on every table sharing that topic grows for hours (Backpressure). - The connector framework's offset store and the source-side slot disagree after a crash, so the connector resumes from a position the source has already advanced past, skipping the difference silently.
What is actually happening
- A CDC connector holds a position in the source's log and the source retains log segments from some oldest point forward. Capture is possible only while the connector's position is at or after that oldest retained point (Write-Ahead Logging).
- Retention is bounded differently by source, and the difference decides which failure you get. Where the reader's position blocks recycling, falling behind consumes source disk without limit. Where retention is time- or size-bounded independently of readers, falling behind past the boundary loses data without warning (Replication Internals: WAL Shipping, LSNs, Lag and Failover).
- This makes the connector's position a hard deadline rather than a soft one. Everywhere else in a data platform, being late costs freshness and is recovered by catching up. Here, being late past the retention boundary costs the changes themselves — they are gone from the source, and the source is the only place they ever were.
- Once the deadline passes, replay does not exist as an option. There is no partial recovery, no degraded mode and no "catch up more slowly". The only path is a fresh snapshot of current state plus a stream from the current position, which recovers the *state* and permanently loses the *changes* in between (Snapshot and Stream: the Bootstrap Problem).
- A downstream event log changes this picture substantially. Once changes have been published to a broker with its own retention, a transformation bug is recovered by rereading the topic rather than by touching the source at all — which is why a broker between CDC and everything else is worth its cost (Retention and Replay).
- Restart safety comes from the position being committed durably before the work is acknowledged, which is the same checkpoint problem as any stateful consumer and produces at-least-once behaviour by construction (Checkpointing). Note that the two retention windows in this story are independent — the source's log and the broker's topics — and confusing them is how a team concludes it can replay a week when the source retained a day (The Event Log).
The failure table
Every row below has been a real incident somewhere, and the value of the table is the cause column — because four of these rows produce the same downstream symptom and have entirely different causes and responses.
Read the first two rows together. They are the same event, a connector that stopped, with opposite consequences depending on one source configuration. In the first, falling behind costs the database. In the second, it costs the data. There is no configuration where falling behind costs nothing, and knowing which of the two you have configured is the single most important operational fact about your CDC deployment.
The last row is the one to sit with. The mitigation for everything above — an alert on lag — fails silently on a low-traffic source when the metric chosen is seconds-behind, because a metric derived from the newest event seen reports zero when no events are being produced (The Average Was Fine and Users Were Not).
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Connector stopped, slot retained | Source disk usage climbing steadily. No data-quality symptom at all, yet. | The reader's position blocks log recycling, so the source retains every segment since the connector stopped. | Restart the connector and let it drain. If the disk will not survive the drain, the choice is an explicit one: extend the disk, or drop the slot and accept data loss. Decide it consciously and record which you chose. |
| Connector stopped, retention expired | Connector restarts cleanly and reports healthy. Downstream tables are permanently short for the outage window. | The source expires log independently of readers, so the position fell outside the retained window while the connector was down. | Re-snapshot plus stream from the current position. Recovers current state; the changes in the window are gone. Tell consumers explicitly that history is missing (Snapshot and Stream: the Bootstrap Problem). |
| Position not advancing while the process is healthy | Green connector, growing lag, no errors in any log. | A long-running or idle-in-transaction session upstream is holding the log open, so there is nothing decodable to advance past (Replication Lag: Reads That Are Correct and Stale). | Find and end the offending session upstream. The connector needs no intervention — and restarting it, the reflex response, achieves nothing and wastes the window. |
| Connector restart after a crash | A burst of duplicate rows downstream covering the period since the last checkpoint. | Resume from the last committed position redelivers everything after it. This is correct at-least-once behaviour, not a bug (At-Least-Once Delivery). | Nothing, if the sink is keyed and position-guarded. If duplicates are visible, the sink is append-only and that is the actual defect to fix (Idempotent Data Pipelines). |
| Bulk update on a captured table | Lag on every table sharing the topic grows for hours; consumers saturate. | One statement produced one event per affected row, and the pipeline is sized for change rate rather than for statement size (Backpressure). | Let it drain while watching position distance against the retention bound. Longer term, isolate high-volume tables onto their own topics and consumers (Topics and Partitions). |
| Offset store and source slot disagree after a restore | A clean-looking resume with a silent gap in the middle of the stream. | Two independent position stores restored from different points, so the connector resumed from a position the source had already passed. | Treat as data loss for the difference: reconcile against the source, and re-snapshot if the gap cannot be replayed. Then reduce to a single authoritative position store. |
| Lag alert never fires during an overnight stall | A stall discovered in the morning, hours into the retention window. | The alert used seconds-behind, which is derived from the newest event seen and reads zero on an idle source. | Alert on position distance, which is meaningful whether or not events are flowing, and page well before the retention bound rather than at it (Alerts Worth Waking Someone For). |
What each hop can still give you back
During an incident the only question that matters is where the data still exists. Walking the chain from the source outward and asking "what does this hold, and for how long" turns a vague panic into a bounded decision.
There are exactly two independent retention windows in the chain and they are usually configured by different teams for different reasons. The source's log is short, expensive and owned by the database team. The broker's topics are longer, cheaper and owned by yours. Almost every recoverable incident is recoverable because of the second one (Retention and Replay).
The couldCorrupt column is a reminder that each hop is also a place where recovery itself goes wrong. A replay into an append-only sink duplicates; a re-snapshot into a merge leaves deleted keys behind; a manual repair leaves a table at a position that matches nothing.
- Source tables
holds Current state only. Every intermediate value has been overwritten.
could corrupt Nothing from this domain's side — but it is also the proof that a re-snapshot recovers state and never history (Slowly Changing Dimensions).
↑ reads from - Source transaction log
holds Every committed change, from the oldest retained position forward. Short window, production disk.
could corrupt A dropped slot or an expired retention removes changes permanently. This is the deadline; everything else in the chain is downstream of it surviving.
↑ reads from - Connector position
holds One number: where capture has confirmed up to.
could corrupt Advancing it manually skips changes silently. Two position stores can disagree and skip the difference.
↑ reads from - Broker topics
holds Every published change for the broker's own retention — usually much longer than the source's, and much cheaper.
could corrupt Compaction that keeps only the latest value per key preserves state and discards the history a transition metric needs (Retention and Replay).
↑ reads from - Raw landing zone
holds Every event that was ever consumed, retained as long as object storage retention allows.
could corrupt Being deduplicated or transformed on write, which destroys the evidence needed to diagnose the incident in the first place (Keeping Raw History: The Recovery Position and the Liability).
↑ reads from - Current-state model
holds One row per key at its highest applied position.
could corrupt A replay into an append-only sink; a manual repair that leaves the row at a position matching nothing in the stream.
↑ reads from - Serving tables and dashboards
holds Whatever was last published, rendered with complete confidence.
could corrupt Nothing new — but this is where a permanent gap is finally noticed, typically weeks later and by a person (Stale Dashboards).
Two retention windows, two owners. During an incident, establish which of them still contains the affected period before deciding anything else — that single fact determines whether this is a restart or a re-bootstrap.
The query that tells you which incident this is
The runbook's first step should not be "restart the connector". It should be to establish the position distance and compare it against the retention bound, because that comparison decides everything that follows and it takes one query.
What is shown below is the shape of that check rather than any specific source's catalog. Every log-based source exposes some equivalent of three facts: the current log position, each reader's confirmed position, and the amount of log retained on the reader's behalf. Find those three for your source, wrap them in a query, and put the result on a dashboard next to the freshness numbers.
The second query is the one that catches the stall the first one explains. A connector that is running with a flat position is almost always waiting on an upstream transaction, and the oldest-transaction-age query names the session responsible in seconds rather than in an hour of speculation.
1-- 1. Which incident is this? Position distance per reader,2-- plus how much log is being retained on its behalf.3-- Catalog names differ per engine; the three facts do not.4SELECT5 r.reader_name,6 r.confirmed_position,7 s.current_log_position,8 s.current_log_position - r.confirmed_position AS position_distance,9 r.retained_log_bytes,10 r.is_active11FROM replication_readers r12CROSS JOIN log_state s13ORDER BY position_distance DESC;14 15-- Read it as a decision, not as a number:16-- is_active = false and retained bytes growing -> the source is17-- paying for your outage; the deadline has not passed yet.18-- confirmed_position older than the oldest retained position19-- -> the deadline HAS passed. Replay does not exist.20-- Re-snapshot, and tell consumers history is missing.21 22-- 2. Position flat while the connector is healthy?23-- Capture cannot advance past the oldest open transaction.24SELECT25 session_id,26 state,27 now() - transaction_started_at AS open_for,28 query_text29FROM active_sessions30WHERE transaction_started_at IS NOT NULL31ORDER BY transaction_started_at32LIMIT 10;Neither query is exotic and both are usually written for the first time during the incident they were needed for. The one that pages should be the first: alert on position_distance against the retention bound, early enough that a human can still act, and never on seconds-behind — which reads zero on an idle source and is silent through exactly the overnight stall it exists to catch.
Whether a stalled reader can exhaust source storage, and whether a maximum retained size can be configured to bound it, is source and version dependent and has changed across major releases of the common engines. Verify the current behaviour and the current configuration option names in your source's documentation before relying on either — the difference between "the database fills up" and "the connector loses its position" is exactly this setting.
How to build it
Most important first.
- Alert on position distance, not on process liveness. The metric is the source's current log position minus the connector's confirmed position, compared against the retention bound — and it must page well before the bound, not at it (Alerts Worth Waking Someone For).
- Set an explicit upper bound on how much log a stalled reader may hold, where the source supports one. A bounded slot loses data on breach; an unbounded one loses the database. Choose deliberately and write down which you chose and why.
- Publish to a durable broker immediately and give it retention sized as a recovery window, not as a storage cost. It is the buffer that makes every downstream failure recoverable without a re-snapshot (Retention and Replay).
- Make the sink idempotent by key and guarded by position before the first incident, not after. Replay is the standard response to almost everything here and it is only safe in a sink that was built for it (Idempotent Data Pipelines).
- Write the runbook while nothing is broken, and make its first step "read the position distance and decide which incident this is". Lag and loss have different responses and confusing them wastes the window in which lag is still recoverable.
- Rehearse the re-snapshot, because it is the recovery of last resort and is otherwise attempted for the first time under maximum pressure (Snapshot and Stream: the Bootstrap Problem) — and alert on the source's retained log size independently of the connector, because that metric is where an analytics stall becomes a database emergency and it belongs to the database team as much as to yours (Saturation: The Reading Utilization Cannot Give You).
What this actually promises
Naming the guarantee you do not have is worth more than naming the one you do — everything downstream inherits the weakest promise in the chain.
- CDC guarantees at-least-once delivery of every committed change, ordered by source log position — conditional on the position remaining inside retention. That condition is the whole lesson, and it is the only guarantee in the domain with a deadline attached.
- It guarantees nothing once the deadline passes. Not partial delivery, not a gap marker, not an error at the moment of loss — the connector simply resumes from wherever it can and reports success.
- It does not guarantee deduplication across a restart. Replay from the last committed position redelivers everything after it, every time (Deduplication).
- It does not guarantee that the source-side position and a framework-side offset agree. Two position stores is two things that can be restored inconsistently.
- It does not guarantee transactional atomicity downstream, before or after a failure — a partial catch-up leaves two tables at two positions with no relationship between them (CDC Ordering and Transaction Boundaries).
- A downstream broker guarantees replay only within its own retention, which is a separate and independently configured window from the source's (Retention and Replay).
Can I trust it?
A green pipeline is evidence that code ran. These four fields are the evidence that the data is right.
- The decisive check is a position gap check: assert that the sequence of applied positions per partition has no discontinuity, and alert on the first one. It is the only check that distinguishes late from lost (Data Tests).
- Reconcile key sets and a summed measure against the source for the period covering any outage, once the period is closed. This is what turns "we think we recovered" into evidence (Reconciliation).
- The gap check misses a loss that happened before the check was deployed, and it misses anything lost during a re-snapshot, because a re-snapshot legitimately resets the position sequence.
- The reconciliation misses entities created and deleted entirely inside the lost window — they are absent from the source now and absent downstream, so the two sides agree about something that never arrived (Missing Rows).
- Lag during an incident is unbounded and its shape is a cliff followed by a slow drain: nothing moves while the connector is down, then catch-up is limited by decode and consumer throughput rather than by the size of the backlog (The Backlog Arithmetic: Four Levers and a Drain Time).
- Catch-up is not instant even when everything is healthy, so the freshness SLO should be stated with a stated recovery time rather than as a single number (The Freshness SLO).
- A partially caught-up pipeline is worse than a stopped one for any consumer joining two tables, because one is current and the other is not and nothing says so (CDC Ordering and Transaction Boundaries).
- A re-snapshot resets freshness to "current state now" while permanently leaving a hole in history — which is a different kind of staleness that no freshness metric expresses.
- A connector upgrade can change how the position is stored or interpreted. Upgrading is therefore a recovery-path change and deserves a rehearsal, not just a version bump (CDC and Schema Drift).
- Changing retention on the source shortens or lengthens the deadline for every connector reading it, usually without anyone telling the pipeline team (Data Retention).
- Adding tables to an existing connector restarts it, which means a bootstrap for the new tables and a brief position hold for the existing ones — a routine change with two failure modes attached (Snapshot and Stream: the Bootstrap Problem).
- A source failover or major version upgrade may invalidate the position entirely, because log positions are not portable across a promotion. That is a planned re-bootstrap, and discovering it during the failover is the expensive version (Replication and Read Scaling).
- First, determine which incident this is. If the connector's position is still inside retention, this is lag: restart, let it drain, verify with a reconciliation, and nothing was lost.
- If the position is outside retention, this is loss, and the only path is a fresh snapshot plus a stream from the current position. Current state is recovered; the individual changes in the window are not, and any metric that depended on transitions during it is permanently unanswerable (Snapshot and Stream: the Bootstrap Problem).
- If the source is fine but a downstream transformation was wrong, do not touch the source at all — replay from the broker. This is the cheap recovery and it is available exactly to the extent that broker retention was sized as a recovery window (Replay from the Log).
- Never resume by moving the position forward to "skip the backlog". It converts a recoverable lag incident into an unrecoverable loss incident with one command, and it is a tempting thing to type at 03:00 when a disk is filling.
- After any recovery, reconcile before telling consumers it is fixed, and tell them explicitly if history is missing rather than letting them discover a discontinuity later (Data Incidents).
What can go wrong
- A stalled connector holding the source's log open until the database's disk fills — an analytics failure escalating into a production outage.
- A slot dropped under disk pressure, converting a recoverable lag into permanent loss with no error anywhere.
- A position advanced manually to clear a backlog, with the same effect and no record of what was skipped.
- Two position stores — source-side slot and framework-side offset — restored inconsistently after a crash, resuming from a position that skips changes.
- A bulk change producing a volume the consumers cannot drain, so lag grows for hours and approaches a retention bound nobody is watching (Backpressure).
- The mitigation fails too: an alert threshold set in seconds-behind, which reads zero on an idle database and therefore stays silent through an overnight stall on a low-traffic source.
- "The connector is running, so we are fine." A running connector whose position is not advancing is the most dangerous state in this module, because it looks identical to a healthy one on a liveness check.
- "We can catch up later." Only inside retention. Outside it there is nothing to catch up to — this is the one place in the domain where lateness becomes loss.
- "We have seven days of Kafka retention, so we can replay a week." You can replay a week of what already reached Kafka. Anything that never left the database is bounded by the source's retention, which is usually far shorter (Retention and Replay).
- "Dropping the slot is a safe way to free disk." It is a safe way to protect the database and a guaranteed way to lose data. It is sometimes the right call, and it should be made knowingly rather than as a routine cleanup.
- "Seconds-behind is the lag metric." It is derived from the newest event seen and reads zero when nothing is being produced, so it is silent during exactly the stall it should catch. Position distance is the metric (The Average Was Fine and Users Were Not).
- "A re-snapshot puts us back to normal." It restores current state. Every change in the lost window — every transition, every intermediate value, every deleted row — is gone permanently (Slowly Changing Dimensions).
- Longer log retention on the source means committed changes — including personal data and including values later corrected or deleted — persist in the log for the retention period regardless of what the tables now hold (Data Retention).
- A recovery re-snapshot re-imports rows that may have been removed downstream in response to a deletion request, so deletion requests must be reapplied after every bootstrap rather than treated as done (Deletion Requests).
- A data-loss incident is a completeness incident with reporting consequences. If a regulated report was produced from a period with a known gap, that is a disclosure question and not only an engineering one (Data Governance).
Operating it
- Position distance: source current position minus connector confirmed position, per connector, with the retention bound drawn on the same axis. This is the single most important chart in a CDC deployment (Pipeline Metrics).
- Retained log bytes per replication slot on the source, alerted independently by the database team (Saturation: The Reading Utilization Cannot Give You).
- Oldest transaction age on the source, because a long-lived transaction stalls capture without stalling the connector (Replication Lag: Reads That Are Correct and Stale).
- Consumer lag per partition rather than aggregate, so one stuck partition inside a healthy total is visible (The Backlog Arithmetic: Four Levers and a Drain Time).
- A boolean derived signal — "is the connector position still inside retention" — surfaced on the same dashboard as the data freshness numbers, because it is the fact that decides whether an incident is recoverable (The Data Quality Dashboard).
- At 10x change volume, catch-up after an outage takes proportionally longer while the retention window does not grow, so the effective deadline shortens as the system grows (Capacity Planning: Traffic to Machines).
- At 100x, a re-snapshot may no longer complete in an acceptable window, which means the recovery of last resort has quietly stopped being available and retention monitoring becomes the only defence.
- More captured tables means more positions to track and one shared deadline, since they usually share a slot and a topic set (CDC Ordering and Transaction Boundaries).
- More consumers make catch-up slower, because the same backlog must be drained by every consumer group independently (Consumer Groups and the Parallelism Ceiling).
- The dominant cost driver in this lesson is retained bytes, in two independent places: the source's log and the broker's topics. Both are recovery windows priced as storage (What Actually Drives Data Platform Cost).
- Longer source retention buys a longer deadline and costs source disk that is usually the most expensive storage in the system.
- Longer broker retention buys the cheap recovery path — replay without touching the source — and costs storage that is usually much cheaper. This asymmetry is why the broker is where the recovery window should live (Storage Lifecycle).
- A re-snapshot costs a full table read plus the broker and consumer capacity to carry it, which is the real price of letting a deadline pass (Compute Waste).
- Longer source retention buys recovery time and costs production disk plus the risk that a stalled reader consumes it silently.
- A bounded slot buys protection of the database and guarantees data loss on breach. An unbounded slot buys unlimited recovery time and risks the database. There is no configuration that gives both, and pretending otherwise is the mistake.
- A broker between CDC and everything else buys the cheap replay path and costs a system to operate. It is the highest-value piece of operational cost in this module (Message Brokers: Log-Shaped and Queue-Shaped).
- Alerting early enough to act buys recovery and costs pages on lag that would have drained by itself. Set the threshold where a human can still act, not where the deadline is (Alert Fatigue: The Page Nobody Reads).
CDC retention deadline
Change an input and watch which number moves — and which one does not. Everything here comes from a model in this repository, not from a measurement.
Where this applies
Almost nothing here is universal. These labels say what each claim is specific to, and where a different engine, format, warehouse or scale would differ.
- GENERALEvery log-based capture mechanism has a bounded window in which a reader may fall behind, because no source retains its change log forever. The window length and the failure at its edge differ; the existence of the deadline does not.
- SOURCE-SPECIFICPostgres replication slots block log recycling until the reader advances, so the failure of falling behind is source disk exhaustion rather than data loss — unless a maximum slot size is configured, which converts it into data loss. MySQL binary logs expire on a time or size policy that ignores readers entirely, so falling behind loses data with no upstream symptom. A MongoDB oplog is a capped collection whose time window shrinks as write volume rises, so the deadline itself moves under load.
- BROKER-SPECIFICReplay from a downstream log depends on that broker's retention model: Kafka retains by time or size per topic and can be configured for compaction that keeps the latest value per key indefinitely while discarding history; Kinesis retains for a bounded period only; Pub/Sub retains acknowledged messages only if a topic is explicitly configured for it. The cheap recovery path exists to a completely different degree on each.
Where the depth lives
This domain teaches how data moves and how you know it arrived intact. It hands the rest off by name.
- — Distributed Systems owns what a bounded replay window means for recovery guarantees generally, and why a system whose recovery depends on another system's retention has taken a dependency it usually has not written down. The retention deadline here is a concrete instance of that.
- — DevOps / Production Engineering owns the operational half: the runbook, the rehearsal of a re-snapshot, the alert thresholds, and the fact that a connector outage is a shared incident between the data team and the team that owns the source database's disk.