CephCXXIII · Capacity and Failure PlanningCapacity and Failure Planning
Sizing for one host down, or for two
What you'll learn
- Compute the reserve for one and for two host failures
- Identify correlated domains that make the host the wrong unit
- Express the reserve as a cost per usable byte
- Choose a level deliberately and record it
Prerequisites
None — start here.
Verified against Ceph Tentacle 20.2.x · Ceph Squid 19.2.x (supported previous) · cephadm matches the verified Ceph release · podman 4.x · csi-rbd and csi-cephfs current · RBD / CephFS / RGW current (matches Ceph release) · Linux kernel 5.15+ (5.10 minimum) · Ubuntu 24.04 LTS (Ceph host baseline) · Debian 12 (Bookworm) (Ceph host baseline) · Rocky Linux / RHEL / AlmaLinux 9.x (Ceph host baseline) · Proxmox VE 9.x (cross-course integration) · Kubernetes 1.31+ (cross-course integration) · 2026-08-18
Why this matters in production
The reserve is a line on the purchase order, and its size is decided by how many failure domains you intend to survive rather than by how full you are.
The arithmetic of the reserve
Spread evenly, each host holds one Nth of usable capacity. Surviving k simultaneous losses means keeping k Nths free.
| Hosts | Reserve for one | Reserve for two | Max fill for one | Max fill for two |
|---|---|---|---|---|
| 4 | 25% | 50% | 75% | 50% |
| 6 | 17% | 33% | 83% | 67% |
| 8 | 12.5% | 25% | 87.5% | 75% |
| 12 | 8.3% | 17% | 92% | 83% |
| 20 | 5% | 10% | 95% | 90% |
Hosts are rarely identical, so the requirement is the largest one.
ceph osd df tree -f json | python3 -c '
import sys,json
d = json.load(sys.stdin)
h = [(n["name"], n.get("kb_used",0)/1024**3)
for n in d["nodes"] if n.get("type") == "host"]
h.sort(key=lambda x: -x[1])
for n, t in h:
print("%-16s %8.1f TiB raw used" % (n, t))'
When the host is the wrong unit
| Shared component | Real failure unit |
|---|---|
| Rack PDU or top-of-rack switch | the rack |
| A chassis holding four nodes | the chassis |
| One UPS phase | whatever it feeds |
| A firmware revision applied fleet-wide | potentially every host |
ceph osd crush rule dump | grep -E '"type"|"op"'
ceph osd tree | grep -E 'rack|chassis|host'
If the CRUSH failure domain is rack and each rack holds four hosts, the
reserve is a rack — four times the number you were about to write down.
What the second host costs
| Hosts | Raw bytes per usable byte, one host | Two hosts |
|---|---|---|
| 4 | 4.0x | 6.0x |
| 6 | 3.6x | 4.5x |
| 12 | 3.3x | 3.6x |
| 20 | 3.2x | 3.3x |
At six hosts the second level of tolerance adds a quarter to the cost of every usable byte. At twenty it adds a few per cent. Sizing for two simultaneous losses is a decision that becomes affordable somewhere around twelve hosts and is punitive below eight.
Choosing, and recording the choice
cat >> /secure/ceph-capacity-plan.txt <<'EOF'
failure tolerance sized for: 1 host
rationale: 6 hosts, 2 racks, no shared PDU within a rack
revisit at: 12 hosts, where the second host of reserve costs 8 percent
EOF
Quiz
Knowledge check · 4 questions
Q1. A six-host cluster is sized to survive one host failure. What share of usable capacity must stay free?
Q2. Sizing for two simultaneous host failures costs roughly twice the reserve of sizing for one.
Q3. Decide the failure tolerance a cluster should be sized for.
Eight hosts in two racks, four per rack, each rack on a single PDU. The CRUSH failure domain is `host`. The plan reserves one host worth of capacity.
Q4. Why does the reserve cost less per usable byte on a larger cluster?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Size the reserve against the largest thing that can fail at once, which is often a rack or a chassis rather than a host. Record the physical rationale beside the number, and set a cluster size at which the decision to reserve for two failures should be revisited.
Cross-course references
- Kubernetes: pod anti-affinity across nodes is worthless if the nodes share a hypervisor
- Linux: RAID spare counts are chosen against correlated failure, not independent probability