docs(archreview #11): correct Critical 1 premise — SBR was already active on master, not NoDowning

Akka.Cluster.Hosting's WithClustering enables an SBR downing provider by default
(applies SplitBrainResolverOption.Default when ClusterOptions.SplitBrainResolver is
null), which reads the pre-existing akka.conf keep-oldest block. So the cluster was
NOT running NoDowning before Critical 1 and hard-crash failover already worked — the
typed KeepOldestOption is reinforcing/explicit-in-code, not the sole activator.

Corrects the inaccurate 'HOCON inert / NoDowning / never fails over' framing in:
- ServiceCollectionExtensions.BuildClusterOptions XML comment
- akka.conf split-brain-resolver comment
- docs/Redundancy.md Split-brain section
- SplitBrainResolverActivationTests summary + assertion message (+ method rename)

No code revert (the typed option is correct belt-and-suspenders). Cluster.Tests 29/29.
Surfaced by the #9 hard-kill failover negative control.
This commit is contained in:
Joseph Doherty
2026-07-08 22:34:20 -04:00
parent a25c9ed097
commit eaf78aad90
4 changed files with 49 additions and 37 deletions
+17 -12
View File
@@ -154,23 +154,28 @@ Node A lists Node B's `ApplicationUri` and vice-versa. Validated by `DualEndpoin
## Split-brain
The split-brain resolver is **activated in code** by the typed `ClusterOptions.SplitBrainResolver`
(`KeepOldestOption { DownIfAlone = true }`) set in
`ServiceCollectionExtensions.BuildClusterOptions` — this is what registers
`Akka.Cluster.SBR.SplitBrainResolverProvider`. **Without that typed registration the `split-brain-resolver`
HOCON block in `akka.conf` is inert and the cluster runs Akka's default `NoDowning`**, meaning a
hard-crashed node is never downed and neither the cluster singletons nor the `driver` role-leader ever fail
over (a partition would leave both redundancy sides at ServiceLevel 240 indefinitely). The HOCON block is the
tuning source only: `active-strategy = keep-oldest`, `stable-after = 15s`, `keep-oldest.down-if-alone = on`,
`failure-detector.threshold = 10.0` (its `active-strategy` + `down-if-alone` must stay consistent with the
typed option; `stable-after` lives only in HOCON because the typed option can't express it).
The split-brain resolver is **active by default**: Akka.Cluster.Hosting's `WithClustering` enables an SBR
downing provider whenever `ClusterOptions.SplitBrainResolver` is null (it applies
`SplitBrainResolverOption.Default`, which registers `Akka.Cluster.SBR.SplitBrainResolverProvider`), and that
provider reads the `split-brain-resolver` HOCON block in `akka.conf`. On top of that,
`ServiceCollectionExtensions.BuildClusterOptions` sets the typed `ClusterOptions.SplitBrainResolver`
(`KeepOldestOption { DownIfAlone = true }`) to make the strategy **explicit in code** rather than relying on
the framework default — it is reinforcing, not the sole activator, and yields the same effective behavior. So
the cluster is **not** running `NoDowning`; hard-crashed nodes are downed and both the cluster singletons and
the `driver` role-leader fail over. (Only an *explicit* `NoDowning`, e.g.
`akka.cluster.downing-provider-class = ""`, would leave both redundancy sides at ServiceLevel 240
indefinitely.) The HOCON block carries the tuning: `active-strategy = keep-oldest`, `stable-after = 15s`,
`keep-oldest.down-if-alone = on`, `failure-detector.threshold = 10.0` (its `active-strategy` + `down-if-alone`
must stay consistent with the typed option; `stable-after` lives only in HOCON because the typed option can't
express it).
`keep-oldest` is the correct strategy for a 2-node warm-redundancy pair: under a clean partition the oldest
member (typically the long-running primary) stays up and the smaller (or younger) side downs itself within
~`stable-after` seconds; `down-if-alone` downs a node that loses its only peer. `keep-majority`/`static-quorum`
are wrong for two nodes (no majority in a 1-1 split). The `RedundancyStateActor` on the surviving partition
re-computes from the post-partition `Cluster.State`, and a hard-crashed (not gracefully stopped) node now
triggers the same failover.
re-computes from the post-partition `Cluster.State`, and a hard-crashed (not gracefully stopped) node
triggers the same failover — verified by `HardKillFailoverTests` (its negative control confirms failover
survives removing the typed option, and only breaks under an explicit `NoDowning`).
There is no operator-driven role swap during a partition. Failover is what the cluster does automatically.