You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENT.md
+77-34Lines changed: 77 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,29 +33,43 @@ Prefer fixup commits over amending and force-pushing.
33
33
34
34
## What This Is
35
35
36
-
A standalone Python app that replicates data from a source DuckLake table to N destination DuckLake tables using pyducklake's CDC (Change Data Capture) API. Supports INSERT, DELETE, and UPDATE replication. Single thread, single poll loop, no framework.
36
+
A standalone Python app that replicates data from a source DuckLake table to N destination DuckLake tables using pyducklake's CDC (Change Data Capture) API. Supports INSERT, DELETE, and UPDATE replication. One poll thread reads and buffers; a flush worker pool writes destinations. No framework.
37
37
38
-
Routes rows by a configurable field (e.g. `company`) to per-destination tables. Designed for high fanout (100s-1000s of destinations).
38
+
Routes rows by a configurable field (e.g. `company`) to per-destination tables. Designed for high fanout (measured flat at ~43 destinations/s through 1000 destinations).
per-destination flush workers, seeding, and commit/cursor-gap scenarios both
138
+
with and without process death (safe buffer-loss crashes checked
139
+
unconditionally; phantom-window events conditioned via everCrashed — except
140
+
NoDataLoss and PartitionCorrectness, which are also checked through crash
141
+
windows), checking 9 invariants across 31.4M distinct states (~5 min). Modify the spec when changing
142
+
the CDC algorithm or adding new failure modes — and when designing semantic
143
+
changes, extend the spec FIRST and let TLC pass judgment before implementing.
144
+
Always run `just tlc` after spec changes.
107
145
108
146
## Key Design Decisions
109
147
110
148
-**Config via YAML** with `_env` suffix convention for credential indirection
111
149
-**At-least-once semantics**: no cross-catalog transactions; destinations tolerate duplicates
112
-
-**State on source DuckLake**: `_viaduck_state` table tracks per-destination cursors
113
-
-**LRU connection pool**: bounds memory at high fanout (default 50 open connections)
114
-
-**Per-destination error isolation**: one broken destination doesn't block others
115
-
-**Grouped CDC reads**: destinations at the same cursor share a single CDC call
150
+
-**Buffered delivery**: reads at poll cadence, writes at flush cadence (default 120s) — decouples lag visibility from write amplification; `workers: 1, flush_interval_seconds: 0` reproduces unbuffered behavior
151
+
-**State on plain Postgres**: cursor advances must not create catalog snapshots (the snapshot treadmill); lives in a dedicated `viaduck` schema so it never pollutes the ducklake catalog's namespace; upserts carry a monotonicity guard
152
+
-**LRU connection pool with lease pinning**: bounds memory at high fanout (default 100 open connections); eviction never closes a connection mid-transaction
153
+
-**Per-destination error isolation**: one broken destination doesn't block others; a failed flush drops only that destination's buffers
154
+
-**Grouped CDC reads**: destinations at the same read position share a single CDC call
116
155
-**Scan-based seeding**: new destinations bulk-load from a filtered source scan instead of replaying CDC history. Configurable via `seed_mode` (default: `scan`)
156
+
-**Worker threads are a concurrency knob, not a CPU multiplier**: Arrow's compute pool and DuckDB's threads are process-global underneath every flush worker — see README "Worker-thread sizing"
117
157
118
158
## Module Layout
119
159
120
160
| Module | Responsibility |
121
161
|--------|---------------|
122
-
|`main.py`| Entry point, poll loop, 3-phase CDC algorithm, signal handling |
162
+
|`main.py`| Entry point, poll loop, Phase 1 preimage resolution, seeding, signal handling |
163
+
|`delivery.py`| DeliveryManager: per-destination buffers, flush triggers, worker pool, position model |
0 commit comments