Pre-existing uncommitted work: ha-repl-hardening payload data plane (7.x), heartbeat liveness fix (Req 1.3), connection-reuse ASAN fixes, doc cleanup. New: ha-consistency-invariants umbrella (requirements/design/research) and its four implementation sub-specs (specs generated via batch, cross-spec review applied), steering roadmap + product.md Raft+CP decision.
3.2 KiB
3.2 KiB
Brief: ha-wal-replication
Problem
WAL entries are written to disk on the primary but never transmitted to standbys.
repl_broadcast() is a no-send stub (casts payload to void, only increments counters).
repl_wait_majority_ack() returns immediately because the replicator's standbys[]
array is never populated from the HA layer's standby_endpoints. The result:
job submits succeed locally but are invisible on standbys and not durable across failover.
Current State
repl.c:repl_add_standby(),repl_remove_standby(),repl_broadcast(),repl_wait_majority_ack()all exist with correct signatures.repl_broadcastis a stub (line 196-197:(void)payload; (void)payload_len).persist.c:persist_wal_sync_append()callsrepl_broadcast→repl_wait_majority_ack→ advancescommitted_lsn. The call chain is wired; the send is what's missing.ha.c/config_repl.c:ha->standby_endpointsmanages per-standby connections (promoted vote eps, late-joiner connects). Nobody callsrepl_add_standby()from here.cluster.c:OPCode_walAckhandler exists (ha_repl_ack_received) but is unreachable because standbys never receive WAL entries to ACK.
Desired Outcome
- WAL entries written on the primary are transmitted to all connected standbys.
- Each standby applies received WAL entries and sends an ACK.
- The primary advances
committed_lsnonly after a majority of standbys ACK. - Job submits, state transitions, and queue changes survive primary failover.
Approach
Bridge the replicator and HA layer:
- Register: When the HA layer connects to a standby (promoted vote ep or late-joiner),
call
repl_add_standby()to register that endpoint with the replicator. - Deregister: When a standby disconnects (RDHUP / err_handler), call
repl_remove_standby(). - Send: Fill in
repl_broadcast()to serialize the WAL header + payload into anOPCode_walAckor similar message and enqueue viaxce_request(). - ACK: On the standby, when a WAL entry is applied, send a reply carrying the
acked LSN. On the primary,
handle_wal_ackcallsrepl_on_ack().
Scope
- In: repl_broadcast TCP send, HA→replicator standby registration/deregistration,
standby-side WAL ACK reply, integration with existing
repl_wait_majority_ack. - Out: Changes to the heartbeat-driven decision loop (WAL streaming via NOP replies is already wired in ha-repl-hardening). Snapshot transfer path. Config replication path.
Boundary Candidates
repl_broadcastsend implementation (repl.c)- Standby registration bridge (ha.c / config_repl.c → repl_add_standby)
- Standby WAL ACK reply (cluster.c / ha.c)
Upstream / Downstream
- Upstream: ha-config-bootstrap (repl_broadcast API, repl_wait_majority_ack), ha-repl-hardening (WAL catch-up payload, standby-side apply_wal_entry)
- Downstream: None — this is the last missing link
Existing Spec Touchpoints
- Reopens: ha-config-bootstrap task 7.6 (repl_broadcast TCP send deferred)
- Adjacent: ha-repl-hardening (WAL payload already wired to standby apply path)
Constraints
- Must use the existing
repl_add_standby/repl_remove_standbyAPI - Must reuse
OPCode_walAckopcode for the ACK path - Standby WAL apply must be idempotent (replay-safe)