CDC Pipeline System Design Template
CDC pipeline: log-based capture, watermark chunked snapshot, per-key order, schema evolution, outbox, search, cache and warehouse sinks, replay
사용법: Use it when replacing dual writes or nightly exports with a change stream from an operational database. Fill the consumer list and the source log retention budget first, because a stalled replication slot that fills the primary's disk is the classic CDC outage. The main pitfall is treating raw table events as a public contract; use an outbox for cross-team events.
미리보기
System Design: [CDC pipeline name]
Reference design for a change data capture (CDC) pipeline that turns the committed changes of an operational database into an ordered stream of change events, and delivers them to a search index, a cache and a data warehouse. It covers log-based capture, an initial snapshot interleaved with the live log, ordering per key, schema evolution, the transactional outbox pattern, and replay and backfill. Concepts draw on the DBLog watermark approach (Andreakis and Papapanagiotou, arXiv 2010.12597, 2020) and on the concepts documented by Debezium 3.7; the text and the worked design are original. It follows the structure of the System Design Document template. Replace every
[bracketed]value and every row marked Example.
Document control
| Field | Value |
|---|---|
| Document ID | [SDD-CDC-001] |
| System / feature | [Order database change stream] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Implemented / Superseded] |
| Owner | [@owner] |
| Author(s) | [@author] |
| Reviewers | [@reviewer-1], [@reviewer-2] |
| Last updated | YYYY-MM-DD |
| Classification | [Internal; change events contain personal data] |
| Source databases | [Example: PostgreSQL 17 primary orders-db] |
| Related | Message Queue System Design (system-design/design-message-queue), Event Sourcing and CQRS (architecture/event-sourcing-cqrs), Data Pipeline Specification (ai-data/data-pipeline-spec) |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Tech lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Database owner / DBA | [@name] | [Decision] | YYYY-MM-DD |
| SRE / operations | [@name] | [Decision] | YYYY-MM-DD |
| Data platform owner | [@name] | [Decision] | YYYY-MM-DD |
| Privacy / security | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft for design review |
| 0.2 | YYYY-MM-DD | [@name] | Example: Replaced lock-and-dump snapshot with watermark-based chunked snapshot |
| 0.3 | YYYY-MM-DD | [@name] | Example: Added outbox table for domain events consumed by other teams |
| 1.0 | YYYY-MM-DD | [@name] | Example: Approved |
Contents
1. Overview
1.1 Problem statement
[Example: The order service writes to PostgreSQL and then, in application code, updates the search index, deletes cache entries and appends to a nightly export for the warehouse. These dual writes are not atomic: when the service crashes between the database commit and the index update, search shows stale orders for days. The nightly export misses rows changed during the export and the warehouse lags by a full day.]
1.2 Proposed solution
[Example: A CDC connector reads the database's write-ahead log through a logical replication slot and publishes one change event per committed row change to a partitioned log, keyed by the table's primary key. Existing rows are captured by a chunked snapshot that runs alongside the live log and is merged with it using low and high watermark markers, so no table locks are needed and the snapshot can pause and resume. Schemas are registered with a compatibility check. Independent consumers update the search index with version-checked upserts, invalidate the cache, and append raw changes to the warehouse, which merges them into tables. Other teams receive domain events through an outbox table captured by the same connector.]
1.3 Decisions requested from reviewers
| ID | Question | Recommended option | Section |
|---|---|---|---|
| Q-01 | Example: How to capture changes? | Log-based CDC from the primary's logical replication | 7.1 |
| Q-02 | Example: How to capture existing rows? | Watermark-based chunked snapshot interleaved with the log | 7.2 |
| Q-03 | Example: What ordering do consumers get? | Total order per primary key; no order across keys | 7.3 |
| Q-04 | Example: Table events or domain events for other teams? | Outbox for cross-team contracts; table events inside the owning team | 7.5 |
| Q-05 | Example: How much log can the slot retain before we drop it? | Cap at 200 GB, alert at 50 GB | 3.4, 9.1 |
| [Q-nn] | [Question] | [Option] | [Section] |
1.4 45-minute interview path
| Minutes | Cover | Output on the whiteboard |
|---|---|---|
| 0 to 5 | Why dual writes fail; which consumers need changes; freshness target | Core requirements FR-01 to FR-05 |
| 5 to 10 | Change rate, event size, log retention, snapshot duration | Partitions 16, retention budget, overnight snapshot |
| 10 to 18 | Capture options and the pipeline diagram | Section 6 diagram |
| 18 to 33 | Deep dive: snapshot interleaved with the log (7.2) and ordering with idempotent consumers (7.3); if the interviewer cares about contracts, use schema evolution and outbox (7.4, 7.5) | Watermark sequence, version-checked upsert |
| 33 to 40 | Failure: connector down, slot growth, replay and backfill | Failure-mode table in 9.1 |
| 40 to 45 | Trade-offs and 10x | Table in section 10 |
2. Requirements
2.1 Core functional requirements
| ID | Requirement | Priority |
|---|---|---|
| FR-01 | Example: Emit one event per committed insert, update and delete of the captured tables, with before and after images | Must |
| FR-02 | Example: Capture the full current state of a table on demand (initial load or repair) without locking it | Must |
| FR-03 | Example: Deliver events for one primary key in commit order | Must |
| FR-04 | Example: Keep the search index, cache and warehouse consistent with the database within the freshness target | Must |
| FR-05 | Example: Replay changes from a past position and backfill selected keys or tables | Must |
| FR-06 | Example: Publish domain events through an outbox in the same transaction as the business change | Should |
| FR-07 | Example: Carry transaction ID and commit position on every event | Should |
| [FR-nn] | [Requirement] | [Must / Should / Could] |
2.2 Non-functional requirements
| ID | Quality | Target | Measured as |
|---|---|---|---|
| NFR-01 | Freshness | Example: p99 below 5 s from commit to event in the log | Heartbeat table written every 10 s |
| NFR-02 | Completeness | Example: No committed change lost; reconciliation diff zero after convergence | Daily row-count and checksum comparison |
| NFR-03 | Source impact | Example: Snapshot adds under 10% CPU on the primary | Database metrics during snapshot |
| NFR-04 | Throughput | Example: 20 000 row changes/s sustained | Load test |
| NFR-05 | Recoverability | Example: Connector restart resumes without gap within 5 min | Chaos drill |
| [NFR-nn] | [Quality] | [Target] | [Measurement] |
2.3 Below the line (out of scope)
- [Example: Multi-master or bidirectional replication between databases]
- [Example: Cross-key or cross-table transactional delivery to consumers; consumers see per-key order only]
- [Example: Capturing changes made outside the database log (bulk file loads that bypass it)]
- [Example: Real-time joins across tables; done by stream jobs (
system-design/design-stream-processing-engine)] - [Item deferred, with the release that will take it]
3. Capacity estimation
3.1 Assumptions
| Input | Symbol | Value | Source |
|---|---|---|---|
| Peak row changes per second | Example: 20 000 | Database statistics x growth | |
| Average row changes per second | Example: 6 000 | Database statistics | |
| Change event size (before + after + metadata, serialised) | Example: 2 KB | Sampled events | |
| Log retention for replay | Example: 7 days | Replay requirement | |
| Log replication factor | Example: 3 | Log cluster policy | |
| Rows in captured tables | Example: 10 billion (4 TB) | Table statistics | |
| Snapshot read rate per worker (throttled) | Example: 50 000 rows/s | Load test against primary | |
| WAL generated at peak | Example: 30 MB/s | Database metrics |
3.2 Throughput and partitions
At a target of 5 MB/s per partition, 8 partitions carry the peak; doubling for growth gives 16 partitions per high-volume table topic. Consequence: partition count is set once with headroom, because adding partitions later changes the key-to-partition mapping and breaks per-key ordering across the change. Consumers can scale to 16 instances per topic.
3.3 Log storage
Consequence: 7-day replay costs about 22 TB of log storage; longer history comes from compacted topics (latest value per key) plus the warehouse raw change table, not from longer log retention.
3.4 Source log retention while the connector is down
The replication slot keeps WAL until the connector confirms it:
Consequence: a connector outage of two hours at peak fills more than 200 GB
of the primary's disk. Set max_slot_wal_keep_size (PostgreSQL 13 and later;
default -1, unlimited) to a cap the disk can absorb, alert well before it,
and keep a runbook for re-snapshot when the cap invalidates the slot.
3.5 Snapshot duration
with parallel table workers. Consequence: a full snapshot is an overnight job, so it must be resumable from the last completed chunk, run alongside live streaming, and throttle itself on primary CPU and replica lag.
3.6 Summary
| Quantity | Estimate | Design consequence |
|---|---|---|
| Peak event rate | 40 MB/s | 16 partitions per hot topic, fixed up front |
| Log storage | ~22 TB for 7 days | Compacted topics and warehouse for longer history |
| WAL growth when stalled | ~108 GB/hour | Slot size cap, early alert, re-snapshot runbook |
| Full snapshot | ~14 h with 4 workers | Chunked, resumable, throttled, interleaved with log |
| Freshness | Seconds | Heartbeat table to measure it |
4. API design
4.1 Change event envelope
Every event has a key (the primary key) and a value envelope. Field names
below are this design's own; Debezium documents a similar envelope with
before, after, source and op.
{
"key": {"order_id": 918273},
"value": {
"op": "u",
"before": {"order_id": 918273, "status": "PAID", "total_minor": 125000, "updated_at": "2026-10-22T09:12:03Z"},
"after": {"order_id": 918273, "status": "SHIPPED", "total_minor": 125000, "updated_at": "2026-10-22T10:40:51Z"},
"source": {"db": "orders", "table": "orders", "lsn": "2F/7A3C1B80", "tx_id": 88123401, "commit_ts": "2026-10-22T10:40:51.213Z", "snapshot": false},
"schema_id": 42
}
}
op | Meaning |
|---|---|
c | Insert |
u | Update |
d | Delete (followed by a tombstone with a null value so compaction removes the key) |
r | Row read by a snapshot chunk |
4.2 Control operations
| Operation | Interface | Purpose |
|---|---|---|
| Start snapshot | POST /v1/connectors/{id}/snapshots with tables and optional key filter | Initial load, repair or backfill (7.6) |
| Stop snapshot | DELETE /v1/connectors/{id}/snapshots/{sid} | Abort a running snapshot |
| Pause or resume | POST /v1/connectors/{id}:pause | Maintenance windows |
| Status | GET /v1/connectors/{id} | Slot lag, last LSN, snapshot progress |
| Register schema | Schema registry POST /subjects/{subject}/versions | Compatibility check before publishing |
Snapshot requests can also arrive as rows in a signal table inside the source database, which lets the request travel through the log in order with the data (a mechanism Debezium documents for incremental snapshots).
4.3 Consumer contract
- Events for one key arrive in commit order; events for different keys have no order guarantee.
- Delivery is at least once: consumers must be idempotent, using
source.lsn(or the outbox event ID) as a version or deduplication key. - A snapshot
revent for a key may arrive aftercoruevents for the same key only if it reflects a newer or equal state (guaranteed by 7.2). - Tombstones mean the key no longer exists.
5. Data model
5.1 Entities
The pipeline's own state is small: offsets, snapshot progress and schemas. The change events themselves live in the log and the stores downstream.
5.2 Storage choices
| Data | Store | Key | Why |
|---|---|---|---|
| Change events | Partitioned log, one topic per table | Primary key | Per-key order, replay by offset |
| Connector offsets and snapshot progress | Small replicated store (a compacted topic or the coordination service) | connector_id, snapshot_id | Resume after restart |
| Schemas | Schema registry | Subject per topic | Compatibility checks |
| Watermark table | Source database, dedicated schema | Single row updated with a new UUID | Markers that appear in the log |
| Outbox table | Source database | event_id | Same transaction as business change |
| Warehouse raw changes | Append-only table partitioned by commit date | (table, pk, lsn) | Audit and replay beyond log retention |
5.3 Access patterns
| Access pattern | Served by | Partition key and rationale |
|---|---|---|
| All changes for one order in order | Topic orders, partition = hash(order_id) | Primary key, so one key's events stay in one partition |
| Resume streaming after restart | Offset store: last_lsn for the connector | One row per connector; written after the log acknowledges |
| Resume snapshot after restart | Chunk table: last completed last_key per table | Chunks are ordered by primary key, so a key is a cursor |
| Apply change to search index only if newer | Index document version = lsn as a number | External versioning rejects older writes |
| Latest state of every key (rebuild a consumer) | Compacted topic | Compaction keeps the last value per key |
| Changes for a time range (audit) | Warehouse raw table partitioned by commit date | Date pruning |
6. High-level design
| Component | Responsibility | Scales by |
|---|---|---|
| CDC connector | Read the log, run snapshot chunks, publish events, commit offsets | One active task per source database (log is a single stream); standby for failover |
| Schema registry | Store schemas, enforce compatibility | Small replicated service |
| Partitioned log | Durable ordered transport, fan-out to consumers (system-design/design-message-queue) | Partitions |
| Search indexer | Version-checked upserts and deletes | Partitions per topic |
| Cache invalidator | Delete cache keys derived from changed rows (system-design/design-distributed-cache) | Partitions per topic |
| Warehouse loader | Append raw changes, periodic merge into tables | Batch size and merge frequency |
7. Detailed design
7.1 Deep dive: how to capture changes
| Option | How it works | Strength | Weakness |
|---|---|---|---|
| A. Dual writes in application code | Service writes database, then index, cache and stream | No new infrastructure | Not atomic; crashes and races leave systems divergent |
| B. Query polling | Poll WHERE updated_at > last_seen | Simple; works on any database | Misses deletes, misses rows with equal timestamps, load on the database |
| C. Triggers into a change table | Trigger writes each change to a table, which is then read | Captures deletes; transactional | Extra write per change inside every transaction |
| D. Log-based CDC | Read the database's own replication log | Every committed change, in commit order, deletes included, little overhead | Database-specific setup; log retention risk (3.4) |
Choice: D. Reason: the log is already the database's source of truth for committed changes, so reading it removes the dual-write gap without adding work to every transaction. Requirements on the source: logical replication enabled, a dedicated slot and publication, primary keys on every captured table, and a replica identity that includes the before image where consumers need it.
7.2 Deep dive: initial snapshot interleaved with the log
The log only holds recent changes, so existing rows need a snapshot, and the snapshot must not overwrite a newer change that the log already delivered.
| Option | How it works | Strength | Weakness |
|---|---|---|---|
| A. Lock and dump | Lock tables, dump, record position, unlock, stream | Simple consistency | Blocks writes; impossible at 4 TB |
| B. Consistent export then stream | Open a repeatable-read export at the slot's start position, dump everything, then stream from that position | No locks; one consistent cut | Streaming waits for a 14-hour dump; not resumable; whole tables only |
| C. Watermark-based chunks | Pause log processing briefly, write a low watermark, select one chunk by primary key, write a high watermark, resume; drop chunk rows whose keys changed between the watermarks, emit the rest at the high watermark | No locks; resumable; runs during streaming; selective by table or key | Requires non-stale reads of the chunk and a watermark table |
Choice: C for both initial load and repairs. Reason: it keeps log latency within NFR-01 during a 14-hour snapshot, survives restarts by chunk, and the same mechanism serves targeted backfills (7.6).
Why it is safe: any key that changed inside the window is delivered by the log event, which is at least as new as the selected row; any key that did not change inside the window has the same value in the chunk as in the database at the high watermark. The selection must read committed data at least as new as the low watermark write, so run it on the primary (or on a replica only after confirming it has replayed past the low watermark).
7.3 Deep dive: ordering and idempotent delivery
| Option | Guarantee | Cost |
|---|---|---|
| A. Global order (single partition) | Total commit order | Throughput of one partition; no consumer parallelism |
| B. Per-key order (partition by primary key) | Commit order per key | No cross-key or transaction ordering |
| C. Per-transaction grouping | Consumers see whole transactions | Buffering large transactions; complex consumers |
Choice: B, with transaction metadata on every event for consumers that need to group. Reason: all three consumers apply changes per key, and per-key order with 16 partitions meets throughput.
Delivery is at least once: after a connector crash it resumes from the last committed offset and republishes some events. Consumers stay correct by version-checking:
- Search: index with external version = numeric LSN; an older or equal
version is rejected, so duplicates and reordering after replay are harmless.
Snapshot
revents carry the LSN of their chunk's high watermark as the version, so they never overwrite a newer logged change. - Cache: always delete (never set) on change; a duplicate delete is harmless.
- Warehouse: append raw changes; the merge picks the row with the highest
(lsn, event order within transaction)per key and ignores duplicates.
Handle primary-key changes as a delete of the old key plus an insert of the new key, so they stay ordered in their own partitions.
7.4 Deep dive: schema evolution
| Option | How it works | Risk |
|---|---|---|
| A. Schemaless JSON | Consumers parse what arrives | Silent breakage on renamed or retyped columns |
| B. Registered schemas with compatibility mode | Each event references a schema ID; the registry rejects incompatible versions | Requires a registry and discipline |
| C. Frozen contract per topic | Only explicit versions, new topic per breaking change | Slow to change |
Choice: B with backward-compatible mode for table topics and C for outbox contracts. Reason: table schemas change often and mostly additively; cross-team contracts deserve explicit versions. Rules:
- Adding a nullable column or a column with a default is allowed.
- Renaming or dropping a column needs a two-step migration: add the new column, backfill, switch consumers, then drop the old one.
- Changing a column type is a breaking change: new column or new topic.
- DDL runs through review that checks the registry first; the connector stops on an incompatible change rather than publishing it.
7.5 Outbox pattern for domain events
Table changes describe storage, not business meaning, and expose internal columns to other teams. For cross-team events the service writes an outbox row in the same transaction as the business change; the connector captures the outbox table and routes rows to topics by aggregate type, keyed by aggregate ID, so events for one aggregate keep their order.
| Column | Purpose |
|---|---|
event_id | Unique ID; consumers deduplicate on it |
aggregate_type | Routes to a topic, for example order |
aggregate_id | Message key; keeps per-aggregate order |
event_type | For example OrderShipped |
payload | The versioned contract |
created_at | Diagnostics; rows deleted after capture or by a retention job |
The outbox removes the dual write without a distributed transaction. It does
not make consumers exactly-once: they still deduplicate on event_id. See
Event Sourcing and CQRS (architecture/event-sourcing-cqrs) when the events
themselves become the system of record.
7.6 Replay and backfill
| Need | Procedure |
|---|---|
| New consumer, full history | Read the compacted topic from the start (latest value per key), then follow the live topic |
| Consumer bug fixed, reprocess 3 days | Reset the consumer group offset to a timestamp within log retention; idempotent writes make it safe |
| Older than retention | Replay from the warehouse raw change table into a replay topic |
| Downstream store lost or corrupt for some keys | Start a snapshot with a key filter; chunks interleave with live changes (7.2) |
| Slot invalidated by the size cap | Create a new slot, start streaming, run a full chunked snapshot; consumers converge by version checks |
7.7 How real systems do it
How real systems do it (paraphrased, not copied). Andreakis and Papapanagiotou describe Netflix's DBLog in "DBLog: A Watermark Based Change-Data-Capture Framework" (arXiv 2010.12597, October 2020). The framework reads the transaction log and, to capture full table state, selects rows in primary-key chunks while log processing continues. A dedicated single-row watermark table is updated before and after each chunk select; when the reader sees the matching events in the log, it drops chunk rows whose keys changed between the two markers and emits the remaining rows at the closing marker, so a selected row never overrides a newer logged change. Chunk progress is stored externally so selects can pause and resume, and they can target all tables, one table or specific keys. The paper reports production use across many Netflix services. Debezium 3.7 documents a comparable incremental snapshot driven by a signal table. This template reuses the idea, not the text.
8. Scalability and performance
| Concern | Approach | Limit before redesign |
|---|---|---|
| Log reading | One connector task per database log | Example: decoding throughput of one task, around the measured peak x 2 |
| Event volume | 16 partitions per hot table topic | Partition throughput x 16 |
| Large transactions | Stream events as decoded; do not buffer whole transactions | Memory if transaction grouping is enabled |
| Snapshot | Parallel per table, throttled chunks | Primary CPU budget (NFR-03) |
| Many databases | One connector per database, shared log cluster | Operational overhead per connector |
| Consumer lag | Scale consumers to partition count | 16 consumers per group |
Freshness budget (NFR-01, Example): commit to WAL flush under 10 ms, logical decoding and publish under 1 s, log replication and acknowledgement under 500 ms, leaving more than 3 s of headroom for consumer polling.
9. Reliability, security and operations
9.1 Failure-mode table
| Component | Failure | Detection | Blast radius | Mitigation | RTO |
|---|---|---|---|---|---|
| Connector | Crash or host loss | Heartbeat table age; task status | All consumers stop receiving changes; WAL grows | Standby takes over from committed offset; duplicates handled by consumers | Example: 5 min |
| Replication slot | Exceeds size cap and is invalidated | Slot status in pg_replication_slots; alert on retained WAL | Gap in the change stream | New slot plus full chunked snapshot | Example: 14 h to full convergence |
| Primary failover | New primary without the slot | Connector cannot find slot | Gap in the stream | Use failover-capable slots where the database supports them; otherwise re-snapshot | Example: 1 h plus snapshot |
| Log cluster | Unavailable | Publish errors | Connector pauses; WAL grows | Retry with backoff; cap and alert as for slot | Log cluster RTO |
| Schema change | Incompatible DDL | Registry rejects; connector stops | All topics of that table | Review gate for DDL; two-step migrations | Example: 30 min |
| Consumer | Bug writes wrong data | Reconciliation diff | One downstream store | Fix, reset offsets, replay (7.6) | Replay time |
| [Component] | [Failure] | [Signal] | [Scope] | [Mitigation] | [Time] |
9.2 Security and privacy
| Threat | Control |
|---|---|
| Change events copy personal data to more systems | Capture only listed columns; mask or tokenise sensitive columns in the connector; data map updated per topic |
| Erasure requests | Deletes flow as events and tombstones; warehouse raw tables follow the erasure procedure; log retention bounded |
| Replication credentials | Dedicated role with replication privilege only; secrets in a vault; rotated |
| Topic access | Per-topic authorisation per consumer service |
| [Threat] | [Control] |
9.3 Observability
| Signal | Metric | Alert threshold |
|---|---|---|
| Freshness | Heartbeat event age in the log | Example: above 30 s for 5 min |
| Source pressure | Retained WAL per slot | Example: 50 GB warning, 150 GB critical |
| Snapshot | Chunks done, rows/s, primary CPU | CPU above budget |
| Consumers | Lag per group | Above 5 min |
| Correctness | Row counts and checksums, database versus each store | Any diff after 1 h convergence |
10. Trade-offs and alternatives
| Decision | Options | Chosen | Why | What we give up |
|---|---|---|---|---|
| Capture | Dual writes; polling; triggers; log | Log-based | Complete, ordered, low overhead | Database-specific setup and slot risk |
| Snapshot | Lock and dump; consistent export; watermark chunks | Watermark chunks | No locks, resumable, selective | Watermark table; primary reads |
| Ordering | Global; per key; per transaction | Per key | Parallelism with correct per-entity state | No cross-entity order |
| Contract | Table events; outbox events | Both, by audience | Internal speed, external stability | Two kinds of topics to govern |
| Delivery | At least once; exactly once | At least once plus idempotent consumers | Simple, robust | Every consumer must version-check |
| At 10x load | 200 000 changes/s, 400 MB/s | Split the database or capture per shard; more partitions planned from the start; consider decoding on a replica where supported | One log reader per database becomes the bottleneck | More connectors to run |
| At 0.1x load | 2 000 changes/s | Fewer partitions (2 to 4) per topic; a single shared connector; snapshot time is unchanged because it depends on table size, not change rate | Simpler operations | Little; polling may even be acceptable for non-critical tables |
11. Delivery plan and risks
11.1 Milestones
| Milestone | Scope | Exit criterion | Target date |
|---|---|---|---|
| M1 | Connector, slot, orders topic, warehouse loader in shadow | Reconciliation diff zero for 7 days | YYYY-MM-DD |
| M2 | Chunked snapshot of all captured tables | Snapshot within CPU budget; restart drill passes | YYYY-MM-DD |
| M3 | Search indexer and cache invalidator; remove dual writes | No stale search results in 14 days of sampling | YYYY-MM-DD |
| M4 | Outbox for cross-team events | First two consumer teams migrated | YYYY-MM-DD |
11.2 Rollout and rollback
[Example: Run CDC consumers alongside the existing dual writes, compare outputs, then disable dual writes per consumer behind a flag. Rollback re-enables dual writes; the CDC pipeline keeps running so it can resume.]
11.3 Risks
| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-01 | Example: Stalled slot fills the primary's disk | Medium | High | Size cap, alerts, runbook (3.4, 9.1) | [@name] |
| R-02 | Example: Snapshot load degrades checkout latency | Medium | High | Throttle on CPU; off-peak schedule | [@name] |
| R-03 | Example: Teams consume table topics as a public contract | High | Medium | Topic access policy; outbox for external teams | [@name] |
| R-04 | Example: Primary failover loses the slot | Medium | High | Failover-capable slots or tested re-snapshot | [@name] |
| [R-nn] | [Risk] | [L / M / H] | [L / M / H] | [Mitigation] | [@name] |
12. Appendices
12.1 Open questions
| ID | Question | Owner | Due |
|---|---|---|---|
| OQ-01 | Example: Can snapshot selects run on a replica with a replay-position check? | [@name] | YYYY-MM-DD |
| OQ-02 | Example: Which columns are masked before leaving the database team? | [@name] | YYYY-MM-DD |
| [OQ-nn] | [Question] | [@name] | YYYY-MM-DD |
12.2 Glossary
| Term | Meaning |
|---|---|
| CDC | Change data capture: producing a stream of committed changes from a database |
| WAL | Write-ahead log, the database's durable record of changes |
| LSN | Log sequence number, a position in the WAL |
| Replication slot | Database object that remembers how far a consumer has read and retains WAL until then |
| Watermark (CDC) | A marker row change written to the log to bracket a snapshot chunk; not the stream-processing watermark |
| Tombstone | A null-value message that lets log compaction remove a deleted key |
| Outbox | Table written in the business transaction whose rows are published as events |
| [Term] | [Meaning] |
12.3 Related templates
- Message Queue System Design (
system-design/design-message-queue): the partitioned log that carries change events. - Distributed Cache System Design (
system-design/design-distributed-cache): consumer that invalidates on change. - Stream Processing Engine System Design (
system-design/design-stream-processing-engine): joins and aggregations over change streams. - Event Sourcing and CQRS (
architecture/event-sourcing-cqrs): when events become the system of record. - Data Pipeline Specification (
ai-data/data-pipeline-spec): contracts and quality checks for the warehouse load.
12.4 References
| Reference | Use |
|---|---|
| Andreakis A., Papapanagiotou I., "DBLog: A Watermark Based Change-Data-Capture Framework", arXiv 2010.12597, 2020 | Watermark-based chunked snapshot (7.2, 7.7) |
| Debezium documentation 3.7: PostgreSQL connector, incremental snapshots, Outbox Event Router | Event envelope concepts, signal table, outbox routing |
PostgreSQL 18 documentation, replication settings (max_slot_wal_keep_size) | Slot retention cap (3.4) |
| Kleppmann M., Riccomini C., Designing Data-Intensive Applications, 2nd ed., 2026 (inspiration only) | Background on CDC and derived data |
Standard and licence note
Based on the publicly described DBLog technique and on concepts in the Debezium 3.7 and PostgreSQL 18 documentation cited in 12.4; no standard governs CDC pipelines. This is a reference design to adapt, with illustrative numbers marked Example. Original text written for this collection; sources were used as inspiration only and no text, figures or diagrams were copied. Check the current documentation of your database and connector, because replication settings and connector options change between releases. Template licence: MIT.
관련 템플릿
Zanzibar-Style Authorization Service System Design Template
Relationship-based authorization service: relation tuples, namespace config, Check/Expand/List/Watch APIs, consistency tokens, caching, sharding, failure modes
시스템 설계Chat System Design Template (Reference Design)
Real-time 1:1 and group chat: WebSocket gateways, per-conversation ordering, fan-out, receipts, offline sync, push, E2EE
시스템 설계Collaborative Editing System Design Template (Reference Design)
Real-time co-editing: OT vs CRDT, sequence CRDT, state-vector sync, presence, offline merge, snapshots and compaction, access