feat: add ZooKeeper dual-active registry sync connector#504
Conversation
|
IDC2 ZooKeeper runtime acceptance for this PR head is complete enough to move out of draft. Pinned candidate:
Runtime evidence:
Boundary:
|
|
Follow-up for the new head after rebasing/merging current Pinned candidate:
Why the branch changed:
Verification on the new head:
Boundary:
|
|
Follow-up: GitHub CI is now green for head Completed checks:
Review boundary remains the same as the previous comment: current-head focused IDC2 ZK runtime evidence is N=2 PASS so far; the older N=5 focused runtime evidence belongs to previous head |
|
Additional focused IDC2 runtime expansion for current head |
|
Additional focused IDC2 runtime expansion for current head |
|
Current-head focused IDC2 runtime expansion for PR #504 head
Current-head focused total is now N=8, PASS=8, FAIL=0, SKIP=0:
Narrow cleanup proof after this run:
Boundary: this is focused IDC2 runtime evidence for the current PR head and review readiness. It is not a full release-ready claim. The older |
|
Current-head focused IDC2 runtime expansion for PR #504 head
Current-head focused total is now N=10, PASS=10, FAIL=0, SKIP=0:
Narrow cleanup proof after this run:
Boundary: this is focused IDC2 runtime evidence for the current PR head and review readiness. It is not a full release-ready claim. The older |
|
Current-head focused IDC2 runtime expansion for PR #504 head
Current-head focused total is now N=12, PASS=12, FAIL=0, SKIP=0:
Narrow cleanup proof after this run:
Boundary: this is focused IDC2 runtime evidence for the current PR head and review readiness. It is not a full release-ready claim. The older |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 02:35 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 04:06 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 04:38 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 05:10 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 05:43 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Current PR route state observed before this run: PR remains open/non-draft at Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 06:16 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Current PR route state observed before this run: PR remains open/non-draft at Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 06:48 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Current PR route state observed before this run: PR remains open/non-draft at Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 07:21 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Current PR route state observed before this run: PR remains open/non-draft at Boundary: old-head evidence on |
|
Current-head focused IDC2 runtime update for PR #504. Scope: focused ZK dt-tests runtime on IDC2 endpoints, using the local dt-tests scaffold against PR head New run pair at 2026-06-12 07:53 Asia/Shanghai:
Current-head focused runtime total after this pair:
Cleanup proof after the run pair:
Current PR route state observed before this run: PR remains open/non-draft at Boundary: old-head evidence on |
|
Current-head IDC2 ZK focused runtime update for
Boundary: this is focused current-head review-readiness evidence for the ZK lane, not a full release-ready claim. |
|
Current-head IDC2 ZK focused runtime update for
Boundary: this is focused current-head review-readiness evidence for the ZK lane, not a full release-ready claim. |
|
Current-head IDC2 ZK focused runtime update for
Boundary: this is focused current-head review-readiness evidence for the ZK lane, not a full release-ready claim. |
|
Current-head ZK focused follow-up for Scope: the two targeted dt-tests that previously covered this PR's ZK regression surface, run against IDC2 endpoints Results at 2026-06-12 09:39-09:40 Asia/Shanghai:
PR state checked at 2026-06-12 09:39 Asia/Shanghai: open, non-draft, head still Conclusion boundary: this is focused current-head runtime evidence for the ZK regression surface, not a full release claim. The remaining blocker appears to be human review / merge ownership, not CI or these targeted ZK tests. |
|
Current-head ZK focused follow-up for Scope: the two targeted dt-tests that cover this PR's ZK regression surface, run against IDC2 endpoints Results at 2026-06-12 10:12-10:13 Asia/Shanghai:
PR state checked at 2026-06-12 10:11 Asia/Shanghai: open, non-draft, head still Conclusion boundary: this is focused current-head runtime evidence for the ZK regression surface, not a full release claim. The remaining blocker appears to be human review / merge ownership, not CI or these targeted ZK tests. |
|
Current-head ZK focused follow-up for Scope: the two targeted dt-tests that cover this PR's ZK regression surface, run against IDC2 endpoints Results at 2026-06-12 10:45 Asia/Shanghai:
PR state checked at 2026-06-12 10:44 Asia/Shanghai: open, non-draft, head still Conclusion boundary: this is focused current-head runtime evidence for the ZK regression surface, not a full release claim. The remaining blocker appears to be human review / merge ownership, not CI or these targeted ZK tests. |
|
Current-head PR #504 follow-up, 2026-06-12 11:20 Asia/Shanghai. State checked before this run:
Additional focused IDC2 ZK runtime evidence on this same head:
Current-head focused total for
Post-run cleanup proof with Kazoo:
Boundary: this is focused current-head runtime regression evidence for the ZK connector surface plus CI state. It is not a full release claim. The visible blocker remains human review / merge ownership, not CI or the targeted ZK runtime pair. |
|
Current-head PR #504 follow-up, 2026-06-12 11:52 Asia/Shanghai. State checked before this run:
Additional focused IDC2 ZK runtime evidence on this same head:
Current-head focused total for
Post-run cleanup proof with Kazoo:
Boundary: this is focused current-head runtime regression evidence for the ZK connector surface plus CI state. It is not a full release claim. The visible blocker remains human review / merge ownership, not CI or the targeted ZK runtime pair. |
|
Keepalive evidence update for PR #504 at c7b403d. PR state refreshed at 2026-06-12 12:23 Asia/Shanghai: OPEN, non-draft, all visible checks passing, reviewDecision=REVIEW_REQUIRED, mergeStateStatus=BLOCKED, with no visible reviewer / review request / assignee in Additional focused IDC2 runtime checks on current head:
Current-head focused ZK runtime total is now N=48 PASS / 0 fail / 0 skip since c7b403d became the PR head: one-way N=24, cycle N=24. Boundary: this is focused ZK connector runtime regression evidence plus visible CI status, not a full release-readiness claim. The visible gate remains human review / merge ownership. |
|
Keepalive evidence update for PR #504 at c7b403d. PR state refreshed at 2026-06-12 12:57 Asia/Shanghai: OPEN, non-draft, all visible checks passing, reviewDecision=REVIEW_REQUIRED, mergeStateStatus=BLOCKED, with no visible reviewer / review request / assignee in Additional focused IDC2 runtime checks on current head:
Current-head focused ZK runtime total is now N=50 PASS / 0 fail / 0 skip since c7b403d became the PR head: one-way N=25, cycle N=25. Boundary: this is focused ZK connector runtime regression evidence plus visible CI status, not a full release-readiness claim. The visible gate remains human review / merge ownership. |
|
PR #504 keepalive after reminder #c5e2fb98, current head c7b403d. Refreshed GitHub state at 2026-06-12 13:29 Asia/Shanghai:
Focused ZK connector runtime evidence on IDC2 ZK endpoints, rerun sequentially after cleanup:
Evidence boundary:
Remaining action: route human reviewer / merge owner for PR #504. |
|
Technical route chosen after the 2026-06-20 #ape-dts follow-up. PR #504 stays draft. We should not continue the old scan-polling extractor as the final production real-time sync route. Chosen route: watch-triggered sync plus reconnect reconciliation. Plain boundary:
Next implementation step:
|
|
Design review addendum from the sinker side. The watch-assisted route also needs a delete/tombstone contract; otherwise an older upsert can recreate data after a newer delete. Required sink-side rule:
This means loop prevention and conflict handling must be based on shadow source metadata for both present and absent business nodes, not only on marker paths. |
|
Option B implementation plan after the 2026-06-20 design follow-up. This is a plan, not implementation/runtime proof. PR #504 should stay draft until these contracts are in code and tested. Product boundary
Code contracts before patching
Patch order
Local recovery note: full implementation plan is in Elizabeth's |
|
Pushed the first Option B code slice: metadata + sinker tombstone contract. Head is now What changed
Tests run locally
BoundaryThis is not runtime validation for Option B yet. PR remains draft. Because the PR head changed from |
|
Follow-up after @vicky's sinker-side contract review. Head is now Changes in
Local validation:
Boundary: CI for |
|
Option B extractor slice pushed. Head is now Changes in
Local validation:
Boundary:
|
|
Follow-up for @vicky's reconciliation-read-error blocker. Head is now Changes in
Local validation:
Boundary: GitHub CI for |
|
Follow-up for @vicky's checkpoint boundary blocker. Head is now Changes:
Local validation:
Boundary:
|
…mplementation skeletons Adds the foundational type system for ZooKeeper dual-active registry sync: - Position::Zk with path_versions checkpoint, high_water_zxid, total_paths - DtData::Zk variant with ZkEntry (path, data, ZkStat, ephemeral, event_type, source_id) - ExtractorConfig::Zk / SinkerConfig::Zk with config loading from ini - ZkFilter (path prefix whitelist/blacklist) and ZkRouter (path prefix remapping) - ZkExtractor / ZkSinker skeleton structs with todo!() implementations - DataMarker ZK support (marker znode path matching and source_id refresh) - ConflictPolicyEnum::LastWriteWins for ZK conflict resolution - DbType::Zk and factory wiring in ExtractorUtil / SinkerUtil Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Extractor, ZkRouter to ZkSinker Blocker fixes: - ZkExtractor: add extract_state, syncer, recovery fields (matching MongoCdcExtractor pattern); remove standalone monitor field - ZkSinker: add ZkRouter field for path prefix remapping Should-fix: - ZkFilter config loaded from [zk_filter] ini section instead of hardcoded; added zk_filter field to TaskConfig with load_zk_filter_config() - ZkRouter: changed from HashMap to Vec<(String, String)> sorted by prefix length descending for deterministic longest-prefix-match - ZkEntry.get_data_size(): include path length in size calculation Nit: - Removed redundant DbType::Zk arm in DataMarker::from_config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the todo!() stub with complete sink implementation: - Lazy ZK client connection via zookeeper-client crate - Serial processing: route path → LWW conflict check → apply event - Event dispatch: Created (upsert), Updated (auto-create), Deleted (idempotent) - LWW conflict policy: compare target stat.mtime vs source mtime - ensure_parent_paths for recursive znode creation - DataMarker writing after each batch (JSON source_id in marker znode) - Ephemeral-to-persistent conversion via sync_ephemeral_as_persistent flag - Metrics tracking via base_sinker.update_serial_monitor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…node 1. LWW deterministic tie-break: read target marker znode once per batch to get existing source_id; when mtime equal, larger source_id wins; if target has no source_id (first sync), default apply 2. Delete NotEmpty handling: log_warn and skip instead of bail, avoids force-deleting parent nodes that have new children from peer 3. write_data_marker uses src_node (fixed identity) instead of data_origin_node (dynamic, refreshed per-event) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full ZkExtractor implementation (~340 lines): - full_scan: recursive znode traversal with path/ephemeral filtering - incremental_rescan: fresh map scan + diff for delete detection, old_versions for version comparison to skip unchanged znodes - Event type correctly set to Created vs Updated based on old_versions - source_id from DataMarker.src_node (not random UUID) for anti-loop - Checkpoint recovery via Position::Zk (path_versions + high_water_zxid) - Heartbeat task writing source_id + position to /__ape_dts_heartbeat Cherry-picked from Elizabeth feat/zk-extractor-impl branch (d33512de). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ZkExtractor: clone position before move to fix borrow checker error - base_struct_sinker, clickhouse_struct_sinker, starrocks_struct_sinker: add wildcard arm for ConflictPolicyEnum::LastWriteWins - base_pipeline: add DtData::Zk to SinkMethod::Raw dispatch All changes pass cargo check --workspace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- docker-compose.zk.yml: two standalone ZK 3.9 instances (ports 2181, 2182) - zk_to_zk/cdc/basic_test/task_config.ini: ZK-to-ZK sync task config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents infinite sync loop in dual-active mode by comparing data bytes before writing. If the target znode already has identical data, the write is skipped, avoiding version bumps that would trigger the peer extractor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ZOO_MY_ID=2 caused config mismatch in standalone ZK 3.9 (only server.1 defined). Changed to ZOO_MY_ID=1 for both independent instances. Added topo1 node1↔node2 test configs with data_marker anti-loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace target-stat-based LWW with shadow metadata at /__ape_dts_shadow/<path> storing (source_id, source_mtime, version). This compares original source mtimes instead of sinker write times, enabling correct conflict resolution in dual-active mode. Also write/delete shadow alongside data operations and add /__ape_dts_shadow to ignore_paths in all test configs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extractor now checks both version AND mzxid when determining if a path is already synced. Fixes missed sync after delete+recreate where version resets to 0 but mzxid advances. - Run cargo fmt to fix CI lint gate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The same-data early return skipped write_shadow, leaving shadow source_mtime stale. LWW decisions then used an outdated watermark. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c4c10d3 to
6b41e7c
Compare
Summary
Key Components
dt-connector/src/extractor/zk/zk_extractor.rsdt-connector/src/sinker/zk/zk_sinker.rsdt-common/src/zk_filter.rsdt-connector/src/zk_router.rsdt-common/src/meta/zk/Anti-Loop Design
In dual-active mode, two tasks run simultaneously (node1→node2 and node2→node1). Without anti-loop:
Solution: Sinker compares data bytes before writing. If target znode already has identical data, the write is skipped — no version bump, no loop trigger.
Test Plan
sinked_recordsstabilizes (no infinite loop)cargo check --workspaceandcargo build --releasepass🤖 Generated with Claude Code
Related Issue
Fixes #523