[redis-ha] Self-heal a masterless set instead of waiting for sentinel - #413
[redis-ha] Self-heal a masterless set instead of waiting for sentinel#413ShmuelOps wants to merge 2 commits into
Conversation
10b1981 to
233a97d
Compare
1fe46ff to
76e1680
Compare
9b4ef02 to
55aee49
Compare
When the master is lost and its replacement rejoins as a replica behind the same announce address, sentinel keeps advertising that address as master and the set ends up with no master at all; sentinel aborts every failover with -NOGOODSLAVE because all candidates look stale. The split-brain container cannot help: it only fixes disagreement with sentinel, and it resolves sentinel through the headless service, which loses all endpoints once every pod is unready. Poll the announce-N services (publishNotReadyAddresses) for whether any member holds role:master. After MASTERLESS_CONFIRMATIONS consecutive observations of none, promote the reachable member with the highest replication offset, repoint the others, and reset the sentinels. The branch where sentinel names this pod as master but it runs as a replica routes into the same healer instead of reinit: reinit re-derives slaveof and shuts down, restarting the pod straight back into a replica while sentinel keeps naming it (DandyDeveloper#383). The misnamed pod is the fastest observer of the masterless state, and promotion stays offset-aware so a freshly replaced empty pod is never promoted merely because a stale sentinel record names it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: ShmuelOps <shmuel@tennasys.com>
bf20adf to
0a3bcb0
Compare
…eset/blind failover Production testing of this healer surfaced two gaps: 1. 'sentinel reset' after direct promotion rediscovers the topology from the sentinel's *configured* master address — the stale one. The replica table comes back empty, every subsequent failover returns -NOGOODSLAVE, and the healer re-triggers forever. Replace the reset with an explicit rewrite of every sentinel's monitor entry (SENTINEL REMOVE + SENTINEL MONITOR at the promoted member), so bookkeeping converges deterministically. 2. Escalation forced SENTINEL FAILOVER on the strength of one sentinel's answer (identify_master load-balances across sentinels via the service). If that answer was merely stale while a healthy master exists, the forced failover demotes it. Both escalation paths now first scan the announce services for whichever member actually holds the master role: if one does, sentinels are repointed at it and no failover happens; only a confirmed masterless set forces a failover (with the existing offset-aware direct promotion as fallback). This also closes the case where sentinel names a pod that is a replica while the real master lives elsewhere — previously confirm_masterless_and_heal aborted and nothing ever fixed sentinel. Verified: chart renders in default / sentinel-auth / TLS modes, the rendered script passes sh -n and shellcheck, and stub-driven checks cover repoint-without-failover, masterless failover+promotion, and self-is-master no-op. End-to-end on a 3-replica release: a sentinel wedged on an address that answers as role:slave with zero candidates (unhealable by the previous revision) converges in ~85s via promote + repoint, with concurrent healer containers on other pods correctly standing down.
|
Pushed ac939b0 with two hardenings found while running this healer in production (PR body updated to match):
|
What this PR does / why we need it:
Fixes the masterless deadlock in #412.
When the master pod is lost and its replacement rejoins as a replica behind the same announce address, sentinel keeps advertising that address as master (it answers pings), every member's local view agrees with sentinel, and the set ends up with no master at all. Replicas loop on
-NOMASTERLINK Can't SYNC while not connected with my master; automatic failovers abort with-failover-abort-no-good-slave/-failover-abort-not-elected, and an explicitSENTINEL FAILOVERreturns-NOGOODSLAVE, because sentinel considers every remaining candidate stale. We hit this in production and it persisted for 7.5h until manual intervention.The
split-brain-fixcontainer cannot help as written, for two reasons:identify_mastercannot resolve the name.MASTERcomes back empty and the existing quorum-reset is equally a no-op.The change. The
announce-Nservices setpublishNotReadyAddresses, so they keep resolving throughout the outage. Poll those for the one fact that matters — does any member holdrole:master. AfterMASTERLESS_CONFIRMATIONSconsecutive observations of none (default 5,MASTERLESS_CONFIRM_INTERVALseconds apart, default 5), promote the reachable member with the highest replication offset viaREPLICAOF NO ONE, repoint the others, and rewrite every sentinel's monitor entry to the promoted member (SENTINEL REMOVE+SENTINEL MONITOR— a baresentinel resetrediscovers from the stale configured address, so the wrong entry survives it and the healer re-triggers).The
reinitcrashloop branch. The branch where sentinel names this pod as master while it runs as a replica used to callreinit, which re-derivesslaveofand shuts the pod down — it restarts straight back into a replica while sentinel keeps naming it, an endless shutdown loop (#383). That branch now heals from data-plane truth: it scans the announce services for whichever member actually holdsrole:master— if one does, sentinels are repointed at it (no failover, so a healthy master behind a stale sentinel answer is never demoted); only a confirmed masterless set goes throughSENTINEL FAILOVERand the offset-aware direct promotion. The misnamed pod is the fastest observer of this state, so it heals on its first detection cycle instead of waiting for its peers. The stale-master escalation path uses the same healer.Safety properties:
role:masteraborts the sequence, so concurrent containers never double-promote, and ordinary sentinel failovers are never interfered with.PROMOTE_SCAN_ATTEMPTStimes, so a member that happens to be mid-restart doesn't postpone recovery.MAX_QUORUM_FAILURESpattern — no new chart values.Relation to #410
#410 addresses the same family of failure (sentinel naming an address whose real role is
slave) by having the named pod promote itself withreplicaof no one. This PR deliberately does not do that, for three reasons:MASTER == ANNOUNCE_IP+role:slaveis exactly what a freshly replaced pod looks like behind a stale sentinel record — sentinel never re-elected it, it just kept the address. Promoting that pod without comparing replication offsets can promote an empty node, and the other replicas (which already point at it) then full-resync from the empty dataset. Here the healer always promotes the highest-offset reachable member — which is the named pod whenever it genuinely is the best candidate.announce-Nservices, which publish not-ready addresses.SENTINEL FAILOVERfirst and promotes directly only after N consecutive masterless confirmations, aborting the moment any master appears.#410's valid core idea — stop the
reinitshutdown loop in that branch (#383) — is folded in here, with the promotion decision kept offset-aware.Which issue this PR fixes
Validation
Verified on an isolated 3-replica release. Fault: point every member at an unreachable address, then
SENTINEL RESET— this reproduces the signature above exactly.The named pod detects the state on its next detection cycle and heals directly (previously 1m57s via the peers' stale-master counters):
Peers, confirming only one member acts:
No false positives: deleting the master pod produces an ordinary sentinel failover — new master within seconds, the deleted pod rejoins as a replica, and no healer on any pod promotes or enters the confirmation loop. It acts only in the state sentinel cannot leave on its own.
helm lintclean; the rendered script passessh -nwith defaults and withauth+sentinel.authenabled.Checklist
[stable/mychartname])