CephXXII · PG InvestigationPG Investigation
Reading a single PG with pg query
What you'll learn
- Run pg query and navigate its output structure
- Locate the recovery_state section and read the current state
- Identify blocked_by and peer information
- Extract the specific fact that explains a stall
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
ceph -s tells you a PG is unhappy. ceph pg query tells you why. It
is a large JSON document and the instinct is to skim it, but almost every
stuck-PG investigation ends at one of four fields, and knowing where they
are turns a twenty-minute read into a thirty-second one.
Running it
ceph pg 7.3d query > /tmp/pg-7.3d.json
jq 'keys' /tmp/pg-7.3d.json
The query goes to the PG’s primary OSD. If the primary is down, the query blocks or fails — which is itself a diagnosis.
The four fields that matter
state — the same string ceph -s shows, at the top level.
jq -r '.state' /tmp/pg-7.3d.json
# active+undersized+degraded
recovery_state — an array of state-machine states, most recent
first, each with an enter_time. This is where you learn how long the PG
has been where it is.
jq -r '.recovery_state[] | "\(.name) \(.enter_time)"' /tmp/pg-7.3d.json
# Started/Primary/Active/Degraded 2026-08-18T02:14:07
# Started/Primary/Active 2026-08-18T02:14:07
# Started 2026-08-18T02:13:55
A PG whose innermost state was entered four hours ago is stuck. One entered forty seconds ago is working.
blocked_by — a list of OSD ids the PG is waiting on. When it is
non-empty, your investigation is over; go look at those OSDs.
jq -r '.recovery_state[] | select(.blocked_by) | .blocked_by[]' /tmp/pg-7.3d.json
peer_info — one entry per peer, each with that peer’s view of the
PG’s version and log. Divergent last_update values across peers explain
why peering has not completed.
A worked read
jq -r '{state, up: .up, acting: .acting,
newest: .info.stats.last_update,
inner: .recovery_state[0].name,
since: .recovery_state[0].enter_time}' /tmp/pg-7.3d.json
{
"state": "peering",
"up": [12, 47, 83],
"acting": [12, 47, 83],
"newest": "41207'8823914",
"inner": "Started/Primary/Peering/GetInfo",
"since": "2026-08-18T01:58:12"
}
GetInfo means the primary is waiting for peers to report their PG info
and has been for over an hour. That is not peering; that is a peer that
is not answering.
Quiz
Knowledge check · 4 questions
Q1. `ceph pg 7.3d query` hangs and never returns. What does this most directly indicate?
Q2. The enter_time on the innermost recovery_state entry tells you how long the PG has been in its current substate.
Q3. Work a stuck PG from ceph -s to a specific cause.
`ceph -s` reports `1 pg peering` for the last 40 minutes. Client I/O to one RBD image is hung. The cluster otherwise reads HEALTH_WARN with no OSDs down.
Q4. You query the same PG three times a minute apart and see recovery_state[0].enter_time change each time, always recent. What is happening?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Capture pg query output to a file at the moment you find a
problem, before you change anything. The state machine moves, and an
intervention that fixes the symptom destroys the evidence. A saved JSON
document is what lets you explain afterwards what was actually wrong,
and it is the single most useful attachment on a Ceph support case.
Cross-course references
- Kubernetes:
kubectl describe podevents serve the same role — a timestamped state history rather than a status word - Linux: reading
enter_timedeltas is the same technique as reading process state durations from /proc