Skip to main content
RunBook Academy

CephCXIV · Complete Storage Node LossComplete Storage Node Loss

How long the rebuild actually takes

Advanced⏱ ~18 minceph

What you'll learn

  • Measure how much work the rebuild has left
  • Produce a defensible completion estimate from observed rate
  • Identify what actually bounds recovery throughput
  • Change the recovery and client IO balance deliberately

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

The exposure window is the rebuild duration, and somebody will ask for that number within the hour. An estimate built from a measured rate is defensible; one built from disk size divided by link speed is not.

Measuring what is left

ceph -s
ceph progress
  data:
    pgs:     3072 active+clean
             1024 active+undersized+degraded+remapped+backfilling
    objects: 12.57M objects, 47 TiB
  io:
    recovery: 1.9 GiB/s, 487 objects/s
  progress:
    Global Recovery Event (18m)
      [=====.......................] (remaining: 1h 47m)
ceph progress json | python3 -c '
import sys,json
for e in json.load(sys.stdin).get("events", []):
    print("%-40s %5.1f%%" % (e.get("message","?")[:40], e.get("progress",0)*100))'

ceph progress gives a remaining time. Treat it as a starting point: it extrapolates from recent rate and revises sharply when the rate changes.

The arithmetic

ceph -s --format json | python3 -c '
import sys,json
p = json.load(sys.stdin)["pgmap"]
deg = p.get("degraded_objects", 0)
tot = p.get("degraded_total", 1)
rate = p.get("recovering_objects_per_sec", 0)
print("degraded: %d of %d" % (deg, tot))
print("rate:     %d obj/s" % rate)
print("eta:      %.1f hours" % (deg / rate / 3600) if rate else "eta: stalled")'
InputWhere it comes from
objects to re-replicatedegraded_objects in the pgmap
achievable raterecovering_objects_per_sec, observed not assumed
concurrency ceilingper-OSD backfill slots across participating OSDs
competing loadthe client share the scheduler is granting
Measure for ten minutes, extrapolate, then measure again. The rate is not
constant: the bulk moves fast and the last few PGs converge slowly,
because by then only a handful of OSD pairs are still working.

What actually bounds it

Candidate boundUsually binding?
Replacement disk write speedno — the writes are spread across every surviving OSD
Network between rackssometimes, on 10G with wide OSD counts
Surviving OSD read capacityoften — the same spindles serve clients
Backfill concurrency limitsoften, and this is the adjustable one
PG countyes at the tail, when few PGs remain
ceph osd df tree | sort -k17 -n | tail -5
ceph tell osd.3 bench 1073741824 4194304 2>/dev/null | head -4

Moving the balance

ceph config get osd osd_mclock_profile
ceph config set osd osd_mclock_profile high_recovery_ops
ProfileEffect
balancedthe default split between client and recovery
high_client_opsprotects latency, lengthens the exposure window
high_recovery_opsshortens the window at measurable client cost
customrequired before the individual settings are honoured
# the old knobs are ignored unless you say otherwise
ceph config set osd osd_mclock_override_recovery_settings true
ceph config set osd osd_max_backfills 4
ceph config set osd osd_recovery_max_active 8
# revert to the profile default when the rebuild is done
ceph config rm osd osd_max_backfills
ceph config set osd osd_mclock_override_recovery_settings false
ceph config set osd osd_mclock_profile balanced

Quiz

Knowledge check · 4 questions

  1. Q1. Raising `osd_max_backfills` during a rebuild produces no change in recovery rate. What is the most likely reason?

  2. Q2. Rebuild duration is bounded by the write throughput of the replacement hardware.

  3. Q3. Produce a rebuild completion estimate for management.

    A node loss left 4.19M of 12.57M objects degraded. `ceph -s` reports recovery at 487 objects/s and rising. The service owner wants a completion time and asks whether it can be made faster.

  4. Q4. Why does the last part of a rebuild take disproportionately long?

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

Production discipline

Build the estimate from degraded_objects and the observed recovery rate, re-measure after ten minutes, and quote a range rather than a time. If you change the mClock profile to shorten the window, write the revert into the same change record — a cluster left on high_recovery_ops pays client latency for every subsequent rebuild nobody is watching.

Cross-course references

  • Kubernetes: a rollout ETA from replica counts ignores the slow tail of the last pods
  • Linux: RAID rebuild speed is bounded by the surviving members, not the new disk