CephCXV · Cluster-Wide Capacity IncidentCluster-Wide Capacity Incident
Adding capacity while the cluster is already at the limit
What you'll learn
- Rank capacity routes by realistic time to usable space
- Predict the transient usage increase that backfill causes
- Recognise when new OSDs cannot be filled at all
- Phase new OSDs in so the movement is paced and observable
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
Adding capacity is the only durable fix and the slowest one, and during the hours it takes it makes utilisation worse. Both facts have to be in the plan before the first disk goes in.
Capacity arrives on three timescales
| Route | Time to usable space | Prerequisite |
|---|---|---|
| Populate spare bays with disks already on site | one to three hours | bays, disks, a reachable host |
| Grow the LV or LUN under an existing OSD | about an hour per OSD | LVM or SAN backing, OSD stopped |
| Spare bays, disks to be procured | days | delivery |
| A new host | days to weeks | rack space, power, network, burn-in |
ceph orch device ls --refresh
ceph orch device ls --format json | python3 -c '
import sys,json
for h in json.load(sys.stdin):
free = [d["path"] for d in h.get("devices", []) if d.get("available")]
if free: print("%-14s %s" % (h.get("addr", h.get("name")), " ".join(free)))'
# growing the device beneath an OSD, where the storage allows it
ceph orch daemon stop osd.14
lvextend -L +2T /dev/ceph-block-vg/osd-block-2f3a
cephadm shell --name osd.14 -- \
ceph-bluestore-tool bluefs-bdev-expand --path /var/lib/ceph/osd/ceph-14
ceph orch daemon start osd.14
Backfill costs space before it saves any
While a PG backfills, the destination accumulates the objects and the
source still holds them. Cluster usage rises for the duration and falls
only as each PG completes and the old copy is released.
ceph df detail | head -6
ceph -s | grep -E 'misplaced|recovery'
| Stage | Cluster usage |
|---|---|
| Before adding | the level you started from |
| During backfill | above it, by roughly the in-flight PG volume |
| After completion | below it, by the added capacity’s share |
The dip is small on a slow ramp and large on an aggressive one, which is the first reason to add weight in stages rather than all at once.
The new OSDs may stay empty
ceph osd df | sort -k17 -n | tail -5
ceph pg ls backfill_toofull | head
ceph osd dump | grep -E 'backfillfull_ratio'
Backfill into an OSD above backfillfull is refused. Backfill out of one
is not — but the PGs that would move onto the new devices are selected by
CRUSH, and if the surviving targets in their acting sets are blocked the
movement does not start.
Phasing the weight in
ceph config set osd osd_crush_initial_weight 0
ceph orch daemon add osd stor-03:/dev/sdk
ceph orch daemon add osd stor-03:/dev/sdl
for w in 2.0 5.0 9.095; do
for id in 56 57; do ceph osd crush reweight "osd.$id" "$w"; done
sleep 900
ceph -s | grep -E 'misplaced|TOTAL'
ceph df | tail -2
done
ceph config rm osd osd_crush_initial_weight
ceph osd pool autoscale-status
Leave pg_num alone during the incident. Splitting PGs onto the new OSDs
is the right long-term correction and the wrong thing to start while
space is short, because the split itself consumes IO and space.
Quiz
Knowledge check · 4 questions
Q1. Eight new OSDs come up healthy on a nearly full cluster but hold almost no data hours later. Why?
Q2. Adding OSDs lowers cluster utilisation from the moment they come up.
Q3. Plan capacity addition during an active capacity incident.
The fullest OSD is at 91%, above backfillfull. Twelve spare bays and matching disks are on site. The cluster runs 9.1 TiB devices at CRUSH weight 9.095.
Q4. Why should CRUSH weight be raised in stages rather than set to full on creation?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Clear the backfillfull condition before deploying new OSDs, or they will come up healthy and stay empty. Bring them in at zero weight and ramp in stages: the transient usage increase during backfill is proportional to what is in flight, and on a cluster with no margin an unstaged ramp can push OSDs over a threshold and stall itself.
Cross-course references
- Kubernetes: a new node cannot help until the scheduler is permitted to place on it
- Linux: an online array reshape needs free space while it runs, not only after