Availability of unanimity: 0.999ⁿ versus a majority
Simulate a modelNeeds Python with NumPy
Problem
Atomic commit across n participants needs every one of them up to decide; consensus needs only a majority. With each node available with probability a, unanimity is available a^n of the time, which falls with every participant, while a majority of a small odd group is available far more than any single node. Correlated failures (a rack, a zone) change the picture: they are rarer than independent ones but take out several nodes at once.
Compute availability for unanimity and majority for n = 1, 3, 5, 10, 20 with a = 0.999. Then check it by random simulation: draw each node up with probability 0.999 for 10 million rounds, count rounds in which all 10 are up and rounds in which at least 3 of 5 are up. Then add correlation: with probability 0.0005 per round, a rack failure takes out several nodes at once.
The question. How available are unanimity and majority, and what do correlated failures change?
Predict
Unanimity over 10 gives about 0.990; a 5-node majority gives about 0.99999999 under independence. With correlation, the unanimity result changes less than expected, while each failure is larger.
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
availability-of-unanimity-0-999-versus.py # /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Availability of unanimity against a majority. Atomic commit needs every participant up; consensus needs a majority. With each node up with probability 0.999: a^n against the binomial majority, for n from 1 to 20; then 10 million simulated rounds for 10-node unanimity and a 3-of-5 majority, independent and with a correlated rack failure (probability 0.0005 per round of losing 5 nodes at once). """ import argparse, json from math import comb import numpy as np p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--a", type=float, default=0.999, help="Node availability.") p.add_argument("--rounds", type=int, default=10_000_000, help="Simulated rounds.") p.add_argument("--rack-p", type=float, default=0.0005, help="Per-round probability that a rack of 5 fails together.") p.add_argument("--seed", type=int, default=0, help="Random seed.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() a = args.a def unanimity(n): return a ** n def majority(n): return sum(comb(n, k) * a ** k * (1 - a) ** (n - k) for k in range(n // 2 + 1, n + 1)) formula = [{"n": n, "unanimity": unanimity(n), "majority": majority(n)} for n in (1, 3, 5, 10, 20)] rng = np.random.default_rng(args.seed); sims = [] for correlated in (False, True): up10 = np.ones(args.rounds, dtype=np.int8); up5 = np.zeros(args.rounds, dtype=np.int16) for i in range(10): up10 &= (rng.random(args.rounds) < a) rack = rng.random(args.rounds) < args.rack_p if correlated else np.zeros(args.rounds, dtype=bool) all10 = up10.astype(bool) & ~rack # the 10 participants span the failed rack for i in range(5): up5 += (rng.random(args.rounds) < a) maj = (up5 >= 3) & ~(rack & (rng.random(args.rounds) < 0.6)) # a rack failure takes 3 of the 5 replicas 60% of the time sims.append({"failures": "correlated rack failures" if correlated else "independent", "unanimity_10": float(all10.mean()), "majority_3_of_5": float(maj.mean())}) for r in formula: print(f"n = {r['n']:>2}: all up {r['unanimity']:.6f} majority up {r['majority']:.10f}") for r in sims: print(f"simulated, {r['failures']:<26} 10 all up {r['unanimity_10']:.5f} 3 of 5 up {r['majority_3_of_5']:.8f}") if args.json: with open(args.json, "w") as f: json.dump({"tables": {"formula": formula, "simulated": sims}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run availability-of-unanimity-0-999-versus.py uv run availability-of-unanimity-0-999-versus.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --a0.999 | Node availability. |
| --rounds10,000,000 | Simulated rounds. |
| --rack-p0.0005 | Per-round probability that a rack of 5 fails together. |
| --seed0 | Random seed. |
Our run
Independent failures, a = 0.999
| n | All up | Majority up |
|---|---|---|
| 1 | 0.999 | 0.999 |
| 3 | 0.997 | 1 |
| 5 | 0.995 | 1 |
| 10 | 0.99 | 1 |
| 20 | 0.98 | 1 |
10 million simulated rounds
| Failures | 10 all up | 3 of 5 up |
|---|---|---|
| independent | 0.99 | 1 |
| correlated rack failures | 0.99 | 1 |
n = 1: all up 0.999000 majority up 0.9990000000
n = 3: all up 0.997003 majority up 0.9999970020
n = 5: all up 0.995010 majority up 0.9999999900
n = 10: all up 0.990045 majority up 1.0000000000
n = 20: all up 0.980189 majority up 1.0000000000
simulated, independent 10 all up 0.99008 3 of 5 up 1.00000000
simulated, correlated rack failures 10 all up 0.98957 3 of 5 up 0.99970680What we found
Unanimity over 10 nodes was available 0.99 of the time, and a 5-node majority 1. The simulation agreed: 0.99 and 1. With rack failures, unanimity barely moved (0.99), while the majority fell to 1: correlation is what breaks a majority, because it takes out several replicas at once.
Read it
Unanimity is fragile by arithmetic and majority is robust only to independent failures. Place replicas so that one correlated event cannot take a majority (across racks and zones), and keep the number of participants in an atomic commit as small as possible.