Repository navigation
Sentinel: re-resolve the primary when it is demoted on a healthy connection - #3281
Conversation
… connection A sentinel-managed connection notices an in-place demotion of its primary (INFO replication reports role:slave) but only re-elects among the nodes it already knows. When sentinel promoted a node at an address the client has never seen and the +switch-master announcement was missed, there is no primary among them and nothing asks sentinel again: writes fail with "No connection (requires writable - not eligible for replica)" until restart. The sentinel connection's heartbeat now checks, from local state only, whether each managed connection's sentinel-reported primary is connected and reporting itself as a replica (or was never added). If so, a recovery attempt runs off the heartbeat thread, at most one per managed connection: - it asks sentinel first; no answer: retry on a later heartbeat; - sentinel names a node held as a connected replica (sentinel has not failed over yet, or our view of that node's role is stale): re-read that node's role with one INFO replication, rather than reconfiguring every node once per heartbeat for as long as sentinel lags (where INFO cannot be sent, fall through to the switch rather than ask sentinel forever); - otherwise switch, as +switch-master would have. An attempt is skipped while the managed connection is already reconfiguring, or once either multiplexer is disposed; disconnected primaries are left to the existing reconnect timer. The heartbeat scan also forgets disposed children, as the +switch-master handler does. Tests: an in-process sentinel (InProcessSentinelServer) and a standalone SetReplicaOf on the toy server. The demotion-to-unknown-address test fails without the fix (no primary at all) and passes with it; a second test holds sentinel on the demoted node and checks that sentinel is still polled but the connection is not reconfigured on every heartbeat, then follows sentinel once it catches up. Refs StackExchange#1891, StackExchange#2487, StackExchange#1670
|
looking now 👍 |
…re reshaping the connection - SwitchPrimary now claims the reconfigure gate (activeConfigCause) BEFORE rewriting the endpoint list or retiring servers, via new TryClaimReconfigure/ReleaseReconfigureClaim and a 'claimed' handover parameter on ReconfigureAsync. Previously a reconfigure that started between RecoverDemotedPrimary's pre-checks and ReconfigureAsync's own CAS would refuse the switch's reconfigure AFTER the connection had been reshaped, leaving retired servers and no primary for at least a heartbeat; concurrent switches could also interleave their Clear/TryAdd endpoint rewrites. A lost race now touches nothing and the heartbeat retries. - RecoverDemotedPrimary hands sentinel's already-validated answer to SwitchPrimary (reportedPrimary), so the switch acts on the answer the guards evaluated instead of re-querying and possibly acting on a different one while sentinel flaps - also removing redundant blocking sentinel round-trips from the claimed section. - A successful demotion recovery clears connection.LastException (as the restore handler does) once the reported primary is connected as a primary, so a stale soft-connect discovery failure stops polluting timeout messages after the client is healthy again. - currentSentinelPrimaryEndPoint is now volatile: it is written by SwitchPrimary on worker/handler threads and read by the heartbeat demotion check. - The disposed-child cleanup shared by the +switch-master handler and the heartbeat sweep is extracted into ForgetDisposedChild. - DemotedPrimaryDoesNotReconfigureWhileSentinelStillNamesIt no longer uses wall-clock quiet detection plus a fixed observation window with an exact event count: it waits for a stretch of three sentinel polls with zero configuration changes, re-baselining on stragglers - deterministic on loaded machines, and faster. - docs/Configuration.md documents how a sentinel-managed connection tracks failovers, including demotion-in-place re-resolution.
|
Thanks - the demotion-in-place gap is real and the heartbeat approach here is sound. I've reviewed and pushed a follow-up commit (b4d330a) to this branch addressing a few things found in review; summary below so the changes have context. Race: the switch could reshape the connection and then have its reconfigure refused
Fix: The switch re-queried sentinel after the guards validated a different answer
Smaller items
Validated locally: full Release build clean on all TFMs (warnings-as-errors), the in-process demotion tests green across repeated runs, all 58 sentinel integration tests green against the docker topology, and the long-running |
Scenario
The client uses Sentinel mode (
ServiceName) and is connected to primary A.+switch-mastermessage never reaches the client.Today the client sees the demotion (
INFO replicationreportsrole:slave) but only re-elects among the nodes it knows; all of them are replicas, and sentinel is not asked again. Writes fail until restart:#3130 fixed the case where the new primary is already known; this is the unknown-address case it left untouched. We hit this class of failure in production (Valkey + Sentinel on Kubernetes).
Fix
On the sentinel connection's heartbeat (every second), using local state only: if the primary sentinel last reported is connected but reports itself as a replica, run one recovery attempt in the background:
INFO(no full reconfigure);+switch-masterwould have.Skipped while a reconfigure is running, after dispose, or when disconnected (the existing reconnect timer handles that). No public API change, no periodic sentinel polling. This goes slightly beyond the sentinel client guidelines ("Handling reconnections"), which rely on sentinel disconnecting the demoted node's clients.
Tests
In-process sentinel (
InProcessSentinelServer) plusSetReplicaOfon the toy server:Also checked against real Valkey 8.1 + Sentinel in docker: the client switched ~1 s after noticing the demotion.
Refs #1891, #2487, #1670
Checklist