「集群很慢」与「集群不健康」的诊断起点不同。前者可能来自磁盘、来自网络、来自 PG peering 占住锁、也可能来自 mClock 配错把前台 op 降了优先级;后者可能是 OSD 宕机、满盘、checksum 不一致。若不先选轴,从「调整 osd_op_num_threads」开始,往往绕一大圈还没找到根因。
本文是系列第 16 篇:按五轴映射症状,并给出核对顺序与观测入口。不粘贴未执行命令输出,字段含义见第 15 篇。
本文是「Ceph / RADOS 存储内核」系列第 16 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 15 篇 · 可观测 ceph status/pg dump/ OSD perf 口径第 16 篇 · 排障坐标系 slow ops、peering、满盘、恢复拖死、checksum 第 17 篇 · 对照替代路径 MinIO / 云块 / 本机 NVMe+SPDK
版本锚定:Ceph Squid v19.2.5。
一、先选轴,再动参数
轴一:Slow Ops — 客户端 op 长时间 pending,业务超时或延迟飙升
轴二:Peering 卡死 — PG 长期 peering/activating,部分数据不可访问
轴三:满盘 — OSD 达到 nearfull/full 阈值,写入被限制或拒绝
轴四:恢复拖死前台 — recovery/backfill 与前台 I/O 争 OSD 资源
轴五:Checksum 错误 — scrub/deep-scrub 发现副本不一致或位翻转
每轴固定三列:症状 → 先查什么 →
不要先做什么。并行乱调
osd_op_num_threads、MTU、mClock
profile,会毁掉因果链——和 SPDK
排障 的「一次否证一轴」同一纪律。
flowchart TD
symptom["症状:超时 / 延迟升 / 写入拒绝 / 不一致"]
symptom --> axis["选轴"]
axis --> a1["轴一 Slow Ops"]
axis --> a2["轴二 Peering"]
axis --> a3["轴三 满盘"]
axis --> a4["轴四 恢复拖死"]
axis --> a5["轴五 Checksum"]
| 轴 | 症状(触发选轴) | 先查 | 不要先做 |
|---|---|---|---|
| 一 | 业务超时 + slow_requests / OSD log
slow request |
health detail → 目标 OSD
dump_ops_in_flight(卡在哪一等待态)→
kv_sync_lat |
加
osd_op_num_threads;改客户端超时「先扛住」 |
| 二 | PG inactive / peering 久;该 PG 上 RBD/FS/RGW hang | osd tree 谁 down → pg query 的
blocked_by / LES |
立刻 force-create-pg;未确认 LES 就
force-recovery |
| 三 | nearfull/full;写返回 ENOSPC | osd df tree 单盘 USE% |
只看 ceph df 集群均值;无限抬
full_ratio |
| 四 | 有 recovering/backfilling 且前台延迟升 | 对比 recovering OSD 与其它 OSD 的 commit
latency;iostat |
同时狂降 recovery 又狂升 client 权重「两边都拧」 |
| 五 | inconsistent / checksum mismatch / 读
EIO |
pg query scrub_errors 类型 → OSD log
哪副本件坏 → SMART |
未看磁盘就 pg repair |
默认入口顺序(可裁剪):health detail
→ pg dump_stuck → osd perf
→(慢)dump_ops_in_flight
→(peering)pg query。
二、轴一:Slow Ops(慢操作)
症状
- 客户端写/读延迟升高或超时,但
ceph -s显示HEALTH_WARN且含slow_requests。 - OSD log 出现
WRN N slow requests(阈值:osd_op_complaint_time,默认 30 秒)。 - 业务层(RBD、CephFS、RGW)请求队列积压。
核对路径
第一步:定位哪些 OSD 有 slow op
ceph health detail 中的 slow_request
通常会列出涉及的 OSD ID 与 PG。若未列出,检查各 OSD
日志(/var/log/ceph/ceph-osd.{id}.log)中的
slow request 行,确认是 Primary 还是 Replica
慢。
第二步:查当前进行中的 op
ceph tell osd.{id} dump_ops_in_flight
输出每个 op
的已停留时长与当前所在的处理阶段(waiting for osd_op_tp_lock、waiting for subop、waiting for snap_trimmer
等)。若 op 卡在「waiting for subop」,说明是某个 replica
OSD 慢;若卡在「waiting for osd_op_tp_lock」,说明是 OSD
线程池争用(CPU 或 I/O 内部并发瓶颈)。
第三步:分层定位
| op 卡在哪 | 倾向的根因 |
|---|---|
waiting for subop |
replica OSD 磁盘/网络慢 |
waiting for kv / bluestore
状态 |
Primary 的 BlueStore WAL(RocksDB sync)慢;见第 8 篇 |
waiting for snapset |
snap_trimmer 占住锁;快照删除密集时出现 |
waiting for osd_lock /
thread pool |
OSD 内部并发瓶颈(CPU 或 op 队列深度不足) |
waiting for scrub |
scrub 占用 PG 锁,导致正常 op 排队 |
第四步:排除磁盘 I/O 慢
perf dump 里
bluestore.kv_sync_lat、state_kv_queued_lat
是磁盘/WAL 慢的直接信号;宿主机 iostat -x 的
%util/await 作旁证。
不要先做:加
osd_op_num_threads(磁盘饱和时只加锁争用);先改业务超时;在未分清
Primary/Replica 前重装网卡驱动。
三、轴二:Peering 卡死
症状
ceph health detail报PG peering或Inactive PGs。ceph pg dump_stuck inactive列出长期 inactive 的 PG。- 访问该 PG 上的数据(RBD 读写、CephFS 元数据、RGW object)的客户端 hang。
核对路径
第一步:确认 OSD 状态
ceph osd tree 查看哪些 OSD 是
down。Peering 卡死的最常见原因是 primary OSD 或
peering 所需的某个 replica OSD down。若 down OSD 超过
osd_mon_report_interval(默认 5 秒),MON
会标记为 down;若超过
osd_down_out_interval(默认 300 秒),MON
自动标记为 out
并触发重新分配(re-peering)。
第二步:查 PG peering 状态
对每个 stuck PG 执行
ceph pg {pgid} query(向 primary OSD
查询),输出包含:
peering_blocked_by:哪个 OSD 的回复缺失(pending peer)。probe_targets:正在 probe 的 OSD 列表。- 每个 peer 的
last_epoch_started(LES)。
若某个 OSD 在 probe_targets
中但长时间无回应,该 OSD
进程可能卡死(ceph daemon osd.{id} status
无响应),需强制 kill 并重启;若 OSD 已 down,MON
应已触发超时 out,此时等待 OSD map 更新完成 peering。
第三步:last_epoch_started
不一致
若 PG 有足够的 OSD up,但 peering 仍卡,可能是 LES
分歧:某个 OSD 的 LES 比其他人低太多,其 PG log
跨度不够覆盖最新 epoch。此时 peering 等待那个 OSD 补齐;若该
OSD 数据已过时且无法恢复,需要
ceph pg force-recovery {pgid}(迫使 primary
用现有最佳副本
activate,接受数据可能过时的风险)。这是有损操作,需评估可接受损失。
第四步:incomplete PG
incomplete 的 PG
需要最高级别介入。先确认是否有对应 OSD
仍在但未启动(重启可能解决),再考虑
ceph pg force-recovery
作为最后手段。若业务允许数据丢失(日志类数据),也可
ceph osd force-create-pg {pgid} 强制重建空
PG(数据丢失操作,慎用)。
不要先做:未见
blocked_by/LES 就
force-create-pg;在 OSD 仍 down+in
时疯狂 restart 同一坏盘节点而不 out。
四、轴三:满盘(nearfull / full)
症状
ceph health显示HEALTH_WARN含X nearfull osds或HEALTH_ERR含X full osds。- 客户端写入返回
ENOSPC(对应full阈值触发时 OSD 拒绝写入)。
阈值机制
| 阈值 | 配置项 | 默认值 | 触发行为 |
|---|---|---|---|
| nearfull | osd_nearfull_ratio |
0.85 | health WARN,不影响 I/O |
| full | osd_full_ratio |
0.95 | OSD 拒绝所有写入(返回 ENOSPC) |
| backfill_full | osd_backfill_full_ratio |
0.90 | OSD 拒绝接受 backfill 写(保护 full 前的窗口) |
注意:阈值基于单个 OSD
的使用率,不是集群整体。若集群中有几个 OSD
特别满(数据分布不均),整体 ceph df
可能显示容量充足,但部分 OSD 触发 full。
核对路径
第一步:定位哪些 OSD 满
ceph osd df tree 按 OSD
显示使用率(USE%);-f plain
格式便于 grep。找出 USE% 接近或超过 85% 的 OSD。
第二步:短期缓解
- 若数据分布不均:
ceph osd reweight {id} {weight}降低满 OSD 的 CRUSH weight,触发数据向其他 OSD 迁移(会产生 recovery/backfill 流量)。注意数据迁移中磁盘 I/O 升高,可能加重 slow op。 - 若整体接近满:考虑临时调高
osd_full_ratio(ceph config set osd osd_full_ratio 0.97),争取加盘时间。调高后仍需监控是否继续增长;不可无限期拖延加盘。 - 删除不需要的数据(RBD image、CephFS 文件、RGW 对象后触发 GC)。
第三步:根本解决
加 OSD(扩容磁盘),增加 OSD 后 CRUSH rebalance 自动将数据分散。加 OSD 触发大量 backfill,需在业务低峰期进行。
隐患:若 osd_nearfull_ratio
告警未及时处理,很快触发
full,导致集群写入全停。建议在 Prometheus
告警中对 nearfull 配置足够早的告警(建议 USE% > 75%
即开始跟踪)。
不要先做:只盯 ceph df 集群
RAW%;把 full_ratio 一次抬到 0.99
当「扩容策略」;在满盘 OSD 上同时触发大规模 backfill。
五、轴四:Recovery / Backfill 拖死前台
症状
ceph -s显示有recovering或backfillingPG。- 客户端读写延迟升高,但
ceph health detail无 full/peering 告警。 ceph osd perf显示部分 OSD 的 commit latency 明显高于正常水平。
机制回顾
Recovery 和 backfill 是后台操作,与前台 client I/O
共用同一 OSD 的磁盘带宽和网络带宽(详见第 10 篇)。mClock
调度器(Squid 默认)通过为 client
op、recovery、backfill、scrub 分配不同优先级来保护前台,但
mClock 的优先级基于队列调度,当磁盘本身 I/O
耗尽时(%util=100%),调度无法凭空增加
IOPS。
核对路径
第一步:确认是否真的拖死前台
比较 ceph osd perf 中受影响 OSD 的 commit
latency 与正在 recovering 的 OSD。若 commit latency 仅在
recovering OSD 上升高,说明这些 OSD 的磁盘已是瓶颈;若非
recovering OSD 的 latency
也升高,需检查是否有网络拥塞(recovery 流量打满 OSD
之间的网络,导致所有 replica write 慢)。
第二步:限制 recovery/backfill 速率
若确认前台已被影响:
ceph config set osd osd_recovery_max_active_hdd 1 # 每个 OSD 并发 recovery op 上限(HDD)
ceph config set osd osd_recovery_max_active_ssd 2 # SSD 版本
ceph config set osd osd_max_backfills 1 # 每个 OSD 并发 backfill op 上限
ceph config set osd osd_recovery_sleep_hdd 0.1 # recovery op 之间 sleep(s)降低并发和加 sleep 是以「拖长恢复时间」换「保护前台」,需在恢复时间(PG 处于降级)与前台 SLO 之间权衡。
第三步:强制提升某 PG 恢复优先级
若某个 PG 降级严重(副本数 = 1),需加速恢复时:
ceph pg force-recovery {pgid}
ceph pg force-backfill {pgid}标记为 forced_recovery 的 PG 在 mClock
中获得更高权重,但代价是其他 PG 恢复减速,以及对该 OSD
的前台延迟影响更大。
mClock 配置陷阱:Squid 的 mClock 默认
profile 是 high_recovery_ops,在纯 SSD
集群上可能恢复很激进,前台 P99 延迟上升。可切换为
balanced 或
high_client_ops(ceph config set osd osd_mclock_profile balanced)重新平衡。
不要先做:未确认「延迟升与 recovering OSD 同集合」就全局砍 recovery;把轴一的线程旋钮和轴四的 mClock 旋钮同一变更窗口一起拧。
六、轴五:Checksum 错误与 PG inconsistent
症状
ceph health detail报PG {pgid} is inconsistent(deep-scrub 后发现副本不一致)。- 报
bluestore checksum error或read error on osd.{id}行。 - 客户端读取特定对象时返回 EIO。
机制回顾
BlueStore 对写入的每个数据块计算 crc32c 校验(详见第 9
篇)。读取时重新计算并比对;若不一致,记录
checksum mismatch 并在 OSD log
中写入告警,以及通知 MON 更新 health 状态。Deep-scrub
会对每个对象的所有副本做全量 checksum
验证,发现副本间内容不同时标记 PG 为
inconsistent。
核对路径
第一步:确认 inconsistent PG 的来源
ceph health detail 列出 inconsistent PG 的
ID。对每个 inconsistent PG 执行:
ceph pg {pgid} query输出中的 scrub_errors
字段说明不一致的类型:read_error(磁盘读失败)、object_info_inconsistency(对象
size/mtime
不一致)、object_data_inconsistency(数据内容不一致)。
第二步:确认有哪些对象不一致
scrub 结果记录在 Primary OSD 的 log
文件中(/var/log/ceph/ceph-osd.{id}.log),搜索
ERR obj、data_digest_mismatch、size_mismatch。对应的对象名、PG
和 OSD 信息都在这里。
第三步:尝试 repair
ceph pg repair {pgid}repair 命令触发一次
deep-scrub,并在发现不一致对象时将少数派副本(错误副本)用多数派副本(正确副本)覆盖。前提:多数派副本的
checksum
是正确的——若磁盘发生多点故障导致多数副本损坏,repair
可能覆盖正确的少数副本。在执行 repair 之前,应先通过 OSD log
确认哪个副本是可信来源。
第四步:硬件失败的判断
若同一 OSD 反复出现 checksum
error(read_error
类型),通常是磁盘坏块。此时: - 检查 OSD 宿主机的
dmesg 与 smartctl -a /dev/{disk}
确认磁盘状态。 - 若磁盘已损坏,将该 OSD 标记为
out(ceph osd out {id}),触发数据恢复到其他
OSD,再替换磁盘。
不要直接 repair 而不检查磁盘:若磁盘是 silent corruption(静默损坏,checksum 不一致但未触发 I/O error),repair 时写回的数据可能立即再次损坏。此时应先换磁盘再 repair。
不要先做:把 repair
当「清除 WARN 的按钮」;在未区分 read_error vs
data_digest_mismatch 前批量 repair 多个
PG。
七、串联案例(机制推演,非实录)
以下「案例」仅演示选轴顺序,不冒充事故报告,不含伪造日志或
ceph -s 截图。
案例 A:业务写超时,ceph health 报
slow_requests。
先定轴一(slow ops):ceph health detail
确认涉及 OSD ID,dump_ops_in_flight 找到 op
卡在「waiting for subop」。进入 replica 方向:对 replica OSD
做 ceph osd perf,commit latency 高出其他 OSD 2
倍。再看该 OSD 宿主机的
iostat -x,%util=99%。结论:磁盘
I/O 饱和,非网络问题,非 mClock 配置问题。操作:降低该 OSD
的 CRUSH
weight(ceph osd reweight)迁移负载,等待磁盘更换。
案例 B:扩容后 backfill
期间前台延迟上升。
先定轴四(恢复拖死):ceph -s 显示大量
backfilling PG,osd perf 显示参与
backfill 的 OSD commit latency 从 2ms 升到
15ms。验证:iostat 确认磁盘 %util
高于平时
40%。操作:ceph config set osd osd_max_backfills 1(从默认
1
降至更严格限速可视需要),或在业务低峰期执行扩容。此案例中不要同时调
slow op 参数——slow op 是前台延迟的结果,根因在 backfill 占
I/O。
案例 C:deep-scrub 后
HEALTH_WARN inconsistent PG。
直接进轴五:ceph health detail 列出
pg X.Y is inconsistent。对 PG X.Y 执行
query 查看 scrub_errors 类型,若为
data_digest_mismatch,先检查 Primary OSD log
确认哪个副本是错误来源。在对应 OSD 宿主机检查
smartctl,若磁盘 SMART 有 reallocated sectors
增加,先 ceph osd out {id}
等待恢复,再换磁盘。不要在未确认磁盘状态之前直接
ceph pg repair。
八、五轴检查清单(可剪入 on-call runbook)
- 起点:
ceph health detail——看清是哪类 warning/error,对应哪个轴。 - PG
状态:
ceph pg dump_stuck/ceph pg {pgid} query——PG peering/inconsistent/incomplete。 - OSD
延迟:
ceph osd perf——commit latency 异常的 OSD 先排查磁盘。 - 当前 slow
op:
ceph tell osd.{id} dump_ops_in_flight——看 op 卡在哪个阶段。 - 容量:
ceph osd df tree——任何 OSD USE% > 85% 进轴三。 - 恢复速率:
ceph statusio 行有 recovery 吞吐时,确认是否与前台延迟升高同步出现。 - Checksum:每次 inconsistent PG 出现,先查 OSD log 再 repair。
- 配置漂移:确认
osd_mclock_profile、osd_recovery_max_active、osd_max_backfills是否与预期一致(避免升级或 cephadm 重新应用配置后意外改变)。
跨轴提示:slow op(轴一)和 recovery
拖死(轴四)经常同时出现——先确认 slow op 的 op 阶段是否在
waiting for subop(说明 replica OSD 磁盘忙于
recovery),否则两个轴的调优方向不同,同时乱调只会污染因果链。
九、谱系、争论与开放问题
谱系
排障坐标系不是独立发明:它把第 5 篇 peering、第 6 篇读租约、第 8–9 篇 BlueStore、第 10 篇 recovery/mClock 的失败落点收成五条可互斥的轴。学术上对应两层传统——Weil 等 OSDI/PDSW 的「故障域与自主恢复」叙事,以及 Gulati 等 OSDI 2010 mClock 把客户端/恢复/后台流量拆成可加权服务类。本篇贡献是运维可执行的归因顺序,不是新算法。
争论:先调 mClock 还是先换盘
A 侧(配置优先):前台延迟升高时先改
osd_mclock_profile /
osd_max_backfills,假设 I/O
争用可被调度器切开。
B 侧(介质优先):先用
osd perf + 宿主机 %util/SMART
证伪「单盘饱和」;mClock 不调度 RocksDB
compaction 与 BlueStore deferred_finisher(第 10 篇),改
profile 可能只移动症状。
可检验裁决:若降低 backfills 后 commit latency 仍钉在同一
OSD,而 %util≈100%,则判 B;若 latency 随
backfill 并发线性下降且磁盘未饱和,再谈 A。
开放问题
- 五轴对 Crimson/SeaStore
是否同构:Classic 的
dump_ops_in_flight阶段名与 Crimson reactor 路径是否可一一映射?在 Squid 技术预览边界内,不能假设本篇 runbook 直接可搬(入口:第 18 篇 Crimson 边界)。
incomplete有损恢复的可观测门槛:何时允许人工介入「接受数据丢失」?需要哪些 LES / missing / unfound 字段同时满足才不算误判?问题保持开放,禁止把论坛步骤当规范。
参考资料
官方文档(A 级)
- Ceph Squid — Troubleshooting OSDs
- Ceph Squid — Troubleshooting PGs
- Ceph Squid — mClock Scheduler
- 源码:
src/osd/OSD.cc(slow_request 上报)、src/osd/PrimaryLogPG.cc(op 执行阶段)@ v19.2.5
系列内
- 第 9 篇 · BlueStore 读路径(checksum 验证机制)
- 第 10 篇 · Recovery / Backfill / Scrub(mClock 与 reservation)
- 第 15 篇 · 可观测(字段语义与 Prometheus 指标)
→ 上一篇:可观测 · 系列目录 · 下一篇:对照替代路径
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Ceph RADOS】PG 与 Peering:PrimaryLogPG、PG log 与状态机
拆解 PG 命名与生命周期、PrimaryLogPG 的日志式复制设计、pg_log_entry_t 格式、last_epoch_started 与 last_epoch_clean 的语义差异,以及 peering 状态机从 Reset 到 Active 的完整路径。
【Ceph RADOS】BlueStore 写路径:min_alloc_size、deferred/WAL、checksum 与压缩
以 min_alloc_size 为分叉点拆解 BlueStore 的两条写路径:小写经 deferred WAL 先落 RocksDB 再后台应用到裸盘,大写直接落裸盘后原子更新 onode;说明 checksum 计算时机和压缩对分配逻辑的影响。
【Ceph RADOS】BlueStore 读路径:onode、blob、缓存与 checksum 失败模式
跟踪 BlueStore 一次读操作从 onode 缓存查询、extent map 展开、blob 物理位置定位,到 BlockDevice aio 读取和 checksum 验证的完整路径;梳理缓存架构与静默数据损坏的检测边界。
【Ceph RADOS】Recovery、Backfill 与 Scrub:修复路径、reservation 机制与 mClock QoS 边界
区分三种修复机制的触发条件与保证边界:recovery 依赖 PG log 修复分歧对象,backfill 做全量对象枚举拷贝,scrub/deep-scrub 主动扫描校验;重点解释 reservation 如何限流、async recovery 如何减少前台阻塞,以及 mClock 调度器对修复流量的控制边界与局限。