Skip to main content
RunBook Academy

CephCXIV · Complete Storage Node LossComplete Storage Node Loss

Two nodes gone at once

Advanced⏱ ~18 minceph

What you'll learn

  • Separate degraded PGs from inactive ones
  • Interpret a PG that will not peer
  • Compute what the loss does to remaining capacity
  • Choose between waiting, forcing, and restoring from backup

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

Not yet marked complete on this device.

Why this matters in production

A single node loss is a rebuild. Two at once may be a rebuild, an outage, or a restore, and the three are told apart by PG state rather than by how alarming the health output looks.

Which PGs are inactive

ceph -s
ceph health detail | grep -E 'PG_AVAILABILITY|PG_DEGRADED|OBJECT_UNFOUND'
ceph pg ls incomplete
ceph pg ls stale
ceph pg ls down
StateMeaningClient effect
active+undersized+degradedcopies missing, enough to servenone
active+recovery_wait+degradedqueued for repairnone
undersized+degraded+peeredbelow min_sizeIO blocks on those PGs
incompletepeering cannot prove an authoritative historyIO blocks
downa required copy is on an OSD that is not upIO blocks
staleno report from the acting primarystate unknown
Degraded is a schedule problem. Peered, incomplete and down are outages,
and they are outages only for the objects in those PGs.

Reading a PG that will not peer

ceph pg 4.1f3 query | python3 -c '
import sys,json
d = json.load(sys.stdin)
rs = d.get("recovery_state", [])
print("state:", d.get("state"))
for s in rs[:2]:
    print("-", s.get("name"))
    for b in s.get("blocked_by", []):
        print("  blocked_by osd:", b)
    for p in s.get("peer_info", [])[:3]:
        print("  peer:", p.get("peer"))'
ceph osd blocked-by
ceph pg 4.1f3 query | grep -A5 might_have_unfound

might_have_unfound names the OSDs that could still hold the objects peering is missing. If they are the two dead nodes, the objects exist only there and nowhere else.

The capacity arithmetic

ceph df
ceph osd df | tail -3
Losing 2 of 8 hosts leaves 6 to hold what 8 held. A cluster at 65%
becomes 65 x 8/6 = 87% once re-replication completes — past nearfull,
with backfill needing headroom it no longer has.
ceph osd df --format json | python3 -c '
import sys,json
n = json.load(sys.stdin)["nodes"]
up = [o for o in n if o.get("status") == "up" and o.get("kb")]
used = sum(o["kb_used"] for o in up); size = sum(o["kb"] for o in up)
print("surviving OSDs: %d  used %.1f%% of surviving capacity"
      % (len(up), 100.0*used/size))'

Wait, force, or restore

SituationAction
PGs degraded, capacity sufficientwait; the rebuild is the whole answer
PGs peered below min_size, disks intact elsewhererecover an OSD, even read-only, to restore quorum of copies
PGs incomplete, might_have_unfound names only dead OSDsthose objects are gone; restore that pool or namespace
Capacity insufficient after rebuildadd capacity before letting recovery run to completion
# only after the OSD is confirmed unrecoverable and its objects accepted lost
ceph osd lost 31 --yes-i-really-mean-it
ceph pg 4.1f3 mark_unfound_lost delete
ceph osd force-create-pg 4.1f3 --yes-i-really-mean-it

Quiz

Knowledge check · 4 questions

  1. Q1. A PG is reported `incomplete` after two nodes are lost. What has Ceph concluded?

  2. Q2. A PG reported `incomplete` may still hold a complete and readable copy of every one of its objects.

  3. Q3. Triage a simultaneous two-node loss.

    Two of eight hosts are lost together — a shared PDU. The cluster was 65% used. `ceph -s` shows 640 PGs degraded and 41 incomplete.

  4. Q4. What does `might_have_unfound` in a PG query tell you?

Passing score: 75%. Answers are checked in this browser.

Production discipline

Split the PG list into degraded and inactive before doing anything else; they have different causes, different client effects, and only one of them is urgent. Compute post-recovery utilisation before letting re-replication run — losing a quarter of the hosts raises utilisation by a third, and a recovery that hits nearfull stops halfway with the cluster still exposed.

Cross-course references

  • Kubernetes: a quorum-based controller refuses to act rather than act on a stale view
  • Linux: a journal that cannot prove it is current is replayed or refused, never assumed