Fix #774: monitor can assign a primary an unreachable goal state during failover#1165
Open
dimitri wants to merge 3 commits into
Open
Fix #774: monitor can assign a primary an unreachable goal state during failover#1165dimitri wants to merge 3 commits into
dimitri wants to merge 3 commits into
Conversation
…ng failover
Two rules in group_state_machine.c could combine to assign the primary
goal state demote_timeout while it is sitting at wait_primary. There is
no KeeperFSM[] edge from wait_primary to demote_timeout, so the keeper
fatals forever with "does not know how to reach state ... from ...".
Rule 1 ("no healthy standby in the quorum"): reassigns the primary to
wait_primary whenever secondaryQuorumNodesCount == 0. That count drops
to zero the instant a failover candidate leaves SECONDARY (e.g.
converges to prepare_promotion) -- even though the candidate is the
failover in progress, not a lost standby. Guarded with
!IsFailoverInProgress().
Rule 2 ("prepare_promotion -> stop_replication"): assigns the primary
demote_timeout as soon as the candidate reports prepare_promotion,
unconditionally. demote_timeout has a real KeeperFSM[] edge from
primary/join_primary/apply_settings/draining -- but not from
wait_primary. Guarded to only fire when the primary's reported state is
actually one of those four.
Adds a pg_regress test (failover_candidate_leaves_secondary) isolating
each rule via direct state manufacture, following the same technique
stale_primary_report.sql already uses for hard-to-reach states.
Verified via authoritative `make installcheck` (pg_regress in Docker,
all 13 monitor tests + isolation tests pass) and by empirically
reproducing both bugs pre-fix and confirming both fixes independently
load-bearing (temporarily reverting each and observing the failure
reproduce).
before ever reaching PRIMARY Found via the actual CI failure (pgaftest multi-async, test_014_003_ restart_node1): when a primary is killed while still at wait_primary (never got a healthy secondary counted before being promoted the rest of the way), GetPrimaryOrDemotedNodeInGroupFromList still resolves it as primaryNode (its goal state, wait_primary, is writable). The previous fix's Rule 2 guard correctly refuses to assign it demote_timeout (no KeeperFSM[] edge from wait_primary), but the primaryNode == NULL fallback never fires either, since primaryNode is non-NULL -- the candidate is left stuck at prepare_promotion forever. Broadened the fallback rule to also fire whenever primaryNode exists but is not in a state with a real demote_timeout edge (mirroring the guard's own restricted set): there is no live primary to hand off to either way, so promote the candidate directly, exactly as when primaryNode is NULL. Updated failover_candidate_leaves_secondary.sql's Scenario B to assert the corrected behavior: the candidate now reaches wait_primary directly instead of deferring. Verified via: - `make installcheck` (pg_regress in Docker): all 13 monitor tests + isolation tests pass. - Reproduced the CI hang locally with own-tagged Docker images (pgaf774dbg:pg17 / pgaf774dbg:pgaftest) running the actual multi-async pgaftest schedule; confirmed test_014_003_restart_node1 now passes (previously hung for the full 180s timeout).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two rules in
group_state_machine.ccould combine to assign the primary a goal state its ownKeeperFSM[](src/bin/pg_autoctl/fsm.c) has no transition path to reach from its current state, fataling the keeper forever with"does not know how to reach state ... from ...".ProceedGroupStateForPrimaryNode, "no healthy standby in the quorum"): reassigns the primary towait_primarywheneversecondaryQuorumNodesCount == 0. That count drops to zero the instant a failover candidate leavesSECONDARY(e.g. converges toprepare_promotion) — even though the candidate is the failover in progress, not a lost standby.prepare_promotion->stop_replication"): assigns the primarydemote_timeoutas soon as the candidate reportsprepare_promotion, unconditionally.demote_timeouthas a realKeeperFSM[]edge fromprimary/join_primary/apply_settings/draining— but not fromwait_primary. If Rule 1 already reassigned the primary towait_primary, Rule 2 hands out an unreachabledemote_timeout.Fix
IsFailoverInProgress()is true (the candidate already being inprepare_promotioncounts).demote_timeoutwhen the primary's reported state is actually oneKeeperFSM[]has an edge to (primary,join_primary,apply_settings,draining), deferring otherwise — the nextnode_active()round re-evaluates.Testing
Added
src/monitor/sql/failover_candidate_leaves_secondary.sql, apg_regresstest with two scenarios, each isolating one of the two guards via direct state manufacture (the same techniquestale_primary_report.sqlalready uses for otherwise hard-to-reach states). Neither needs the isolation tester — both are pure sequencing issues insideProceedGroupStateFromContext.Verified via:
make installcheck(pg_regress in Docker) — all 13 monitor tests + isolation tests pass.Issues
Fixes #774.
Also looked at #883 and #369 as candidates for the same root cause:
node_metadata.c/node_active_protocol.c, guarding failover-source node selection) — not touching this rule at all.