LeaseMajority
Defined in: src/cluster/downing/LeaseMajority.ts:72
Split-brain resolver that uses an external Lease to break ties
when the cluster splits into equal-sized partitions — the case
where membership-only strategies (KeepOldest, KeepReferee,
KeepMajority) cannot make a deterministic call.
Algorithm per partition observation (one side of the split):
- Run the standard
KeepMajoritymath.- Reachable side has strict majority → down the unreachable side.
- Reachable side is the strict minority → down ourselves (and every reachable peer on this side).
- Equal-size partition (or insufficient info) → start
lease.acquire(). Return no decision while the acquire is pending so the cluster waits. - When acquire resolves:
true→ we are the surviving side. Down the unreachable side.false→ some other side won. Down our own side.
The decide() interface stays sync (the rest of the resolver
pipeline is sync). Async work happens off-band — this strategy
is stateful: the first equal-size observation kicks off the
acquire; subsequent ticks read the cached result.
Lease unavailable (network problem reaching the K8s API):
acquire() rejects → strategy stays in pending state and returns
an empty decision. Better to wait than to risk both sides
surviving.
Slow / hung acquire (#142 split-brain hardening)
If lease.acquire() hasn’t resolved by acquireTimeoutMs, the
defence-in-depth logic kicks in:
- Epoch invalidation — every kickoff captures a monotonic
acquireEpoch. The timeout-recovery bumps the epoch, so a late-arriving result from the timed-out attempt is dropped (it can’t write a staledecision). - Release-on-abandon — we fire-and-forget
lease.release()to undo any acquire that may have succeeded on the wire after we gave up locally. Without this, an acquire that timed out on the client but succeeded on the server leaves the lease record claimed without anyone observing the win — a classic stale-token split-brain vector. - Fail-safe on release failure — if the abandoning release
itself rejects, the lease state is now ambiguous (we may or
may not hold it; we can’t tell). The strategy enters
fail-safe: every subsequent
decide()for the same partition view returns an empty decision, refusing to claim majority until the partition heals (which resets the fail-safe flag).
Fencing tokens (optional)
If the underlying Lease implements acquireWithToken() (K8s
Lease’s resourceVersion, Redis SETNX with counter, etc.), the
strategy uses it instead of plain acquire(). The token isn’t
inspected at decide-time — the local epoch is the source of
truth for “is this result still valid?” — but having the token
means tighter integration with the underlying lease’s native
fencing primitive (e.g. release-with-token semantics, when
future work adds them).
Implements
Section titled “Implements”Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new LeaseMajority(
options):LeaseMajority
Defined in: src/cluster/downing/LeaseMajority.ts:106
Parameters
Section titled “Parameters”options
Section titled “options”Returns
Section titled “Returns”LeaseMajority
Methods
Section titled “Methods”decide()
Section titled “decide()”decide(
view):DowningDecision
Defined in: src/cluster/downing/LeaseMajority.ts:111
Return addresses to forcibly down. The empty set means “not yet — wait for stability or more heartbeats”.
