CephCXVIII · Data Integrity IncidentData Integrity Incident
Recognising a data integrity incident
What you'll learn
- Separate an isolated scrub error from an incident
- Establish the blast radius across PGs, OSDs and hosts
- Bound when the corruption could have started
- Recognise corruption that scrubbing cannot detect
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
One scrub error is a bad sector. Thirty-seven errors across nine PGs whose acting sets all include OSDs on one host is a failing component, and repairing them one at a time overwrites good copies with bad ones.
What the cluster reports first
ceph health detail
HEALTH_ERR 37 scrub errors; Possible data damage: 9 pgs inconsistent
[ERR] OSD_SCRUB_ERRORS: 37 scrub errors
[ERR] PG_DAMAGED: Possible data damage: 9 pgs inconsistent
pg 12.1a4 is active+clean+inconsistent, acting [17,42,8]
pg 12.3c1 is active+clean+inconsistent, acting [17,11,29]
pg 12.7e0 is active+clean+inconsistent, acting [23,17,5]
ceph pg ls inconsistent
for p in $(ceph osd pool ls); do
echo "== $p"; rados list-inconsistent-pg "$p"
done
Establishing the blast radius
# which OSDs appear in the acting set of every inconsistent PG
ceph health detail | awk '/is active.*inconsistent/ {print $NF}' \
| tr -d '[]' | tr ',' '\n' | sort | uniq -c | sort -rn
ceph osd find 17 | python3 -c '
import sys,json
d = json.load(sys.stdin)
print(d["host"], d.get("crush_location"))'
| Correlation | Implicates |
|---|---|
| every error on one OSD | that device |
| errors on every OSD of one host | HBA, expander, cable or host memory |
| errors on OSDs sharing one controller | the controller |
| errors confined to one pool | the write path for that workload |
| errors appearing everywhere after an upgrade | software, not media |
ceph log last 200 info cluster | grep -iE 'scrub|candidate|read error'
ceph crash ls
Bounding when it began
ceph pg 12.1a4 query | python3 -c '
import sys,json
s = json.load(sys.stdin)["info"]["stats"]
print("last scrub: ", s["last_scrub_stamp"])
print("last deep scrub: ", s["last_deep_scrub_stamp"])'
An error is found at scrub time, never at write time. The corruption
arrived somewhere between the previous clean deep scrub and this one —
with the default interval, a window of at least seven days.
Everything that read the object in that window returned bad data or EIO, and everything that copied it — snapshots, clones, last night’s backup — copied the corruption too.
What a clean scrub does not prove
| Claim | Reality |
|---|---|
| the PG was scrubbed, so it is good | good as of that scrub, for the copies compared |
| all copies match, so the data is right | matching copies can all be wrong together |
| no health error, so no corruption | PGs overdue for deep scrub are simply unexamined |
# how stale the deep-scrub coverage actually is
ceph config get osd osd_deep_scrub_interval
ceph health detail | grep -i 'not deep-scrubbed'
Quiz
Knowledge check · 4 questions
Q1. What separates a data integrity incident from a routine scrub error?
Q2. A deep scrub that reports no errors proves the PG holds no corrupt data.
Q3. Triage a burst of scrub errors.
`ceph health detail` reports 37 scrub errors across 9 inconsistent PGs. OSD 17 appears in the acting set of 8 of them; the rest include OSDs 23 and 29. All three are on host store-07.
Q4. What does the last clean deep-scrub timestamp tell you about a newly reported inconsistency?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Establish the correlation pattern before issuing a single repair — errors clustered on one host mean the candidate good copies may have been written by the same failing component. Record the last clean deep-scrub timestamp for every affected PG; it is the only thing that bounds which backups are suspect.
Cross-course references
- Kubernetes: a burst of pod failures on one node is a node problem, not many pod problems
- Linux: checksums verify transport and storage, never the correctness of what was handed to them