myxce/.kiro/specs/ha-wal-replication/research.md
Rongsong Shen 20e59d0b42 checkpoint: bug-851 root cause + raft TLA+ conformance spec family
Investigation & fixes (failover committed-job loss):
- root-cause bug-851 on an owned 3-node cluster: chaotic failover causes
  silent committed-prefix divergence (WAL hdr.term always 0; two leaders
  per term; no (term,lsn) reconcile). Evidence in repro-851/ + INVESTIGATION.
- live-validated fixes 848/849/850 (recovery-on-promotion, advertise real
  applied_lsn, persist-layer contiguity guard); add waldump WAL inspector.

Specs (strict raft.tla conformance — library owns the log):
- TLA-gap-analysis.md: impl vs raft/raft.tla, gaps G1-G6.
- raft-log-replication, raft-commit-safety, raft-sim-conformance: extend
  src/lib/raft to own log/commit/verification via callbacks (specs generated,
  cross-spec review passed).
- ha-term-safety: controller-side adapter + config marker (R1-R7); design done.
- config-upgrade-snapshot: R8 descoped here (deps entity-bearing-snapshots, bug-857).
- roadmap + buglog (851/854/857) + .wolf knowledge updated.
2026-06-21 14:02:15 +00:00

10 KiB
Raw Blame History

Research: HA WAL Replication

This document captures the codebase-grounded research and gap analysis for the ha-wal-replication spec — bridging the WAL replicator (repl.c) and the HA connection layer (ha.c/cluster.c/config_repl.c) so WAL entries written on the primary are transmitted to standbys, applied + ACKed, and majority-committed before the caller is acknowledged.

There was no prior research.md; this file is created fresh.


Gap Re-Audit (2026-06-18)

STATUS: PARTIAL (code-complete; only the live 3-node integration test is open)

Every code path required by the 5 requirement groups is implemented and unit- tested in the controller suite under ASAN. The single open item that is genuine work is 5.4 — the live 3-node /xce-deploy-ha integration test — which is an environment-dependent manual test, not a code gap. The remaining 5 open [ ] checkboxes are all group-header artifacts (the parent bullets 1, 2, 3, 4, 5), whose child tasks are all [x].

Requirement-to-Asset Map

Tag legend: MET (verified in code), Constraint (design boundary, behaves as specified), Unknown (only verifiable on a live cluster), Missing.

Req Statement (abbrev.) Tag Evidence (file:line)
1.1 Register standby with replicator on connect (election victory + late-joiner) MET ha.c:1060-1074 (become-leader promote loop calls repl_add_standby); ha.c:5035-5049 (late-joiner on_standby_connected path)
1.2 Deregister on disconnect (RDHUP / error) MET ha.c:3961-3964 (vote_ep_err_handler → repl_remove_standby); ha.c:5089-5091 (standby_ep_err_handler_primary); ha.c:5258 (evicted batch)
1.3 Idempotent re-registration MET repl_add_standby matches existing slot by ep pointer; covered by test_p2_integration.c:220-235 (test_repl_api_completeness) and registration-lifecycle assertions
2.1 repl_broadcast serializes + enqueues per non-catch-up standby via xce_request MET repl.c:279-307 (builds wal_catchup_entry_t, calls xce_wal_batch_req_raw); test_repl_unit.c:222-231 (send-hook captures payload round-trip)
2.2 Skip standby while in_catch_up set MET repl.c:265-266 (atomic_load in_catch_up → continue)
2.3 Pending-ACK over MAX_PENDING_ACKS → trigger catch-up + skip MET repl.c:268-277 (inline start-catchup + continue); test_repl_majority.c:282 (over-limit eviction)
2.4 No standbys → return 0 immediately MET repl.c:251-257 (early return, WARN)
3.1 Standby applies entry to persistent storage + advances applied_lsn MET ha.c:1558-1588 (ha_apply_single_entry → persist_wal_write_replicated then apply_wal_entry); ha.c:1676 ha_advance_applied_lsn
3.2 After apply, send ACK carrying applied LSN MET ha.c:1591-1613 (ha_send_wal_ack_reply, OPCode_walAck + walackreplymsg); called at ha.c:1650, ha.c:1679
3.3 Apply failure → log, skip, no ACK MET ha.c:1573-1579, 1640-1647, 1666-1672 (return non-zero, no ACK)
3.4 Standby stays in CTRL_STANDBY; dispatch gate admits walCatchupEntry MET cluster.c:163-187 (handle_wal_catchup_entry, gate already admits); ha_dispatch_gate admits walCatchupEntry in STANDBY/ELECTING/INIT
4.1 repl_wait_majority_ack counts standbys ≥ target, quorum incl. self-ack MET repl.c:422+ (repl_wait_majority_ack); test_repl_majority.c, test_commit_engine.c quorum tests
4.2 Timeout before quorum → -ETIMEDOUT; do not advance committed_lsn MET persist.c:1898-1919 (sync_err = repl_wait_majority_ack(...), only advances on success); test_commit_engine.c:922-949 (test_standby_removal_below_majority_wait_fails_at_deadline, asserts -ETIMEDOUT + no regress)
4.3 On WAL ACK → update acked_lsn + signal ack cond MET ha.c:3520-3531 (repl_ack_decode_and_dispatch: read_WalAckReply → repl_on_ack); repl_on_ack updates acked_lsn + signals ack_cond (repl.c:194+)
4.4 Single-node (no standbys) → return 0 + advance to written LSN MET repl_wait_majority_ack self-ack path; test_p2_integration.c:238+ (single-node production-contract test)
5.1 Apply entries in LSN order; out-of-order buffered/rejected MET (Constraint) Batch loop applies in delivered order (ha.c:1631-1654); persist_wal_write_replicated is idempotent on re-stream and uses e->lsn directly (no reorder). Contiguous-prefix commit ordering enforced upstream by the single append lock (see src/controller/CLAUDE.md Commit Ordering).
5.2 DEBUG3/TRACE log opcode + target LSN on transmit MET repl.c:304-305 (TRACE("repl_broadcast: sent lsn=%lu type=%u to %s:%u"))
5.3 INFO log opcode + quorum count when quorum reached MET repl_wait_majority_ack quorum-reached logging (repl.c); 5.x tests assert quorum path
5.4 WARN log on timeout w/ opcode, timeout, per-standby ACK state MET repl.c timeout WARN path; verified by majority-timeout unit test
5.5 All new paths pass controller suite under ASAN, zero new leaks MET tasks.md 5.5 note: 45 tests / 308 assertions pass; ASAN leaks pre-existing in unrelated tests only

Score: 22/22 EARS requirements MET (4.x/2.x/3.x/5.x all verified in code + unit tests). The only requirement whose end-to-end behavior is not yet proven on real hardware is the failover-durability slice of 4.1/5.5 exercised by task 5.4 — that is an Unknown pending the live cluster, not a code Missing.

Open Tasks — Classification

Task Text Classification Notes
1 repl_broadcast TCP send (parent) group-header-artifact children 1.1, 1.2 both [x]; repl.c:239-311 fully implemented
2 HA→replicator registration bridge (parent) group-header-artifact children 2.1–2.3 [x]; bridge wired at ha.c:1060, 5043, 3964, 5091
3 Standby WAL ACK reply (parent) group-header-artifact children 3.1–3.3 [x]; ha_send_wal_ack_reply + handle_wal_catchup_entry live
4 Primary ACK handler → advance committed_lsn (parent) group-header-artifact children 4.1, 4.2 [x]; read_WalAckReply→repl_on_ack at ha.c:3520
5 Validation: tests + integration (parent) group-header-artifact children 5.1, 5.2, 5.3, 5.5 [x]; only 5.4 remains
5.4 Integration: 3-node cluster WAL replication env-dependent-test (the one genuine remaining item) Requires live /xce-deploy-ha cluster; explicitly marked _Blocked_ / not automatable in the unit suite

Doc-drift note (non-blocking): design.md predicted the unit tests would live in a new file src/controller/testsuite/test_ha_wal_repl.c. That file was never created; the 5.1/5.2/5.3 unit tests actually landed in existing files — test_repl_unit.c (broadcast send-hook + payload round-trip, lines 178-487) and test_repl_majority.c/test_p2_integration.c (ACK round-trip, registration lifecycle, over-limit eviction). Coverage is present; only the filename in the design is stale. No action required unless the team wants the design corrected.

Genuine Remaining Work — Options

The only genuine open item is task 5.4 (live 3-node integration test). It is a verification gap, not a code gap.

Prerequisite for 5.4: a running multi-node cluster via /xce-deploy-ha (e.g. /xce-deploy-ha localhost:9991 localhost:9992 localhost:9993). Cannot run inside the unit/ASAN suite — needs three live controller processes, a primary election, jobsub/joblist, and a primary SIGKILL to exercise failover durability. (ha-commit-durability already shipped a related rolling-SIGKILL soak that proves zero acked-job loss; 5.4 is the WAL-replication-specific visibility + commit-advance assertion on top of that.)

Option Description Effort Risk
A — Run 5.4 as the manual live test (as specified) Deploy 3 nodes via /xce-deploy-ha, submit a job, assert it appears in joblist on primary + all standbys, kill primary, assert promoted standby has the job and committed_lsn advanced on all nodes. Mark 5.4 [x] with captured evidence. Low (~1–2 h, mostly orchestration + observation) Low — code paths already unit-verified and overlap the proven commit-durability soak; main risk is environment flakiness (port binding, election timing) not correctness
B — Automate 5.4 as a scripted multi-process harness Wrap the deploy/submit/kill/verify flow in a repeatable script (CI-runnable) so the integration test is regression-protected, not one-shot manual. Medium (~0.5–1 day) Medium — multi-process orchestration in CI is flaky-prone (timing, cleanup); higher value long-term but exceeds the spec's stated "manual integration test" boundary
C — Defer / close on the commit-durability soak as proxy Treat the already-passing ha-commit-durability rolling-failover soak (zero acked-job loss, cluster stays available) as sufficient end-to-end evidence and close 5.4 by reference, without a dedicated WAL-replication run. Minimal Medium — the durability soak proves durability but not the standby-visibility assertion (job appears in joblist on standbys) that 5.4 specifically calls out; closing without that leaves a small explicit coverage hole

Recommendation: Option A — run 5.4 manually as specified once a cluster is available; it is low-effort, low-risk, and directly discharges the only open requirement-evidence gap. Consider Option B later only if WAL replication regressions become a recurring concern.

Bottom line

ha-wal-replication is functionally code-complete: all 22 EARS requirements are implemented and unit-tested under ASAN. The 5 open parent checkboxes are group-header artifacts. The single substantive open item is the live 3-node integration test (5.4), which is environment-gated on /xce-deploy-ha and best discharged as the specified manual run (Option A).