2-node keep-oldest cannot survive an oldest-node crash (survivor downs ITSELF) — evaluate ScadaBridge's auto-down decision #492
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
ScadaBridge proved today (2026-07-21, live on its docker rig + Akka.NET 1.5.62 source) that a 2-node
keep-oldestcluster can never survive a hard crash of the oldest/active node — and OtOpcUa runs the identical posture:KeepOldestOption { DownIfAlone = true }on a 2-node cluster (ZB.MOM.WW.OtOpcUa.Cluster/ServiceCollectionExtensions.cs, Akka.Cluster.Hosting 1.5.62).When the oldest crashes, the younger survivor does NOT take over. It takes
DownReachableand downs itself → total cluster outage until the crashed node is manually restarted. Captured live on the ScadaBridge rig:Root cause (upstream, by design)
Akka.Cluster/SBR/DowningStrategy.cs(1.5.62),KeepOldest.OldestDecision— when the oldest is on the unreachable side:down-if-aloneonly rescues a surviving side with >= 2 members; upstream comments say explicitly that with 2 nodes it prefers keeping the (dead) oldest. The XML doc inServiceCollectionExtensions.cs("the design-intended active node survives… down-if-alone downs a singleton that loses its peer") describes the partition case only — it does not hold for a crash of the oldest, and theActorSystemTerminationWatchdog(#459) exit-and-rejoin path cannot save it either: the self-downed survivor's restart loops onInitJoinunless it is the first seed.Known traps if you fix this (checked and rejected in ScadaBridge)
static-quorumquorum-size 1 looks right but is a trap:IsTooManyMembers(2 > 2*1-1) returns DownAll on ANY unreachability — strictly worse.keep-majority: a 1-vs-1 split keeps the lowest-ADDRESS side — same hole, keyed to address instead of age.lease-majorityneeds a shared lease store both nodes can reach.What ScadaBridge did (decision 2026-07-21, owner-approved: availability over partition-safety)
Switched the downing provider to Akka's built-in
AutoDowning:The leader among the reachable members downs the unreachable peer, so a crash of EITHER node fails over to the survivor (~25s; drill-measured takeover in 28s with the victim still down, all singletons
Younger -> Oldest). Accepted trade: a real network partition produces dual-active (each side downs the other and runs its own one-node cluster) until an operator restarts one side. Note Akka.Cluster.Hosting has no typed option for AutoDowning — ScadaBridge emits it in raw HOCON; OtOpcUa would set it inResources/akka.conf(and drop theSplitBrainResolveroption so Hosting doesn't also register the SBR provider).Full decision record + evidence: ScadaBridge
docs/plans/2026-07-21-auto-down-availability-decision.md(commitcf3bd52f).Follow-up for OtOpcUa
keep-oldest(oldest-crash = total outage, partition-safe) or adoptauto-down(either-crash fails over, partition = dual-active). If OtOpcUa's nodes sit on the same VMs/LAN as ScadaBridge's site pairs, the same reasoning likely applies ("network partitions are less of a risk than if this stops working").ServiceCollectionExtensions.csXML doc claims, re-point the failover integration tests (the F22 deviation notes mention kill-mid-apply was non-deterministic under SBR — under auto-down the oldest-crash direction becomes a testable success path), and check the seed-node ordering note: only the FIRST seed can self-form, which under auto-down only matters for boot-alone (cold start with the peer dead), not for takeover.Already fixed and live-verified — closing.
Code.
2964361a(2026-07-21) switched the downing provider exactly as this issue proposes.AkkaClusterOptions.SplitBrainResolverStrategynow defaults toauto-down,BuildDowningHoconinstalls Akka'sAutoDowningprovider with a non-offauto-down-unreachable-after(the defaultoffis load-bearing and called out in the code), and an unknown value fails host start rather than silently falling back.keep-oldestsurvives only as an explicit opt-in.Live drill. The 1-vs-1 crash-the-oldest gate this issue is really about was closed by the Phase 7 failover drills (
docs/plans/2026-07-24-mesh-phase7-failover-drills.md), 6/6 PASS:site-b-1(Primary and oldest);site-b-2logged "Member removed [site-b-1]", wentBecomingOldest → Oldest, and served ServiceLevel 250.Upand did not self-down (e.g.site-a-1"Up 49 minutes",site-b-2"Up 56 minutes"). This is the drill that closes the gate deferred since Phase 0a.So the
DownReachable-downs-itself pathology this issue captured from ScadaBridge cannot occur on the current default.The accepted trade is unchanged and documented:
auto-downproduces dual-active during a genuine network partition. That was the deliberate availability-over-partition-safety call, recorded inCLAUDE.md§Redundancy and inAkkaClusterOptions.SplitBrainResolverStrategy's XML doc.One correction to the issue's closing note: it suggested setting this in
Resources/akka.conf. It is not there — several HOCON fragments compete and the file is the wrong authority. It is emitted byBuildDowningHoconand Prepended, and the effective config must be asserted off a runningActorSystem(asSplitBrainResolverActivationTestsdoes), never off the typed options or theakka.conftext.