“恢复把前台拖死了”是 Ceph 运维中最常被投诉的问题之一,但”恢复”这个词在 Ceph 内部对应三个完全不同的机制:recovery、backfill 和 scrub/deep-scrub。三者触发条件、数据来源、并发限制和对前台延迟的影响各不相同,混淆它们会导致限速参数配错方向。本篇拆解三个机制的边界,以及 mClock 调度器在 Squid 中对修复流量的实际控制范围。
本文是「Ceph / RADOS 存储内核」系列第 10 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 9 篇 · BlueStore 读路径 onode、blob、cache、checksum 失败模式 第 10 篇 · Recovery / Backfill / Scrub 修复路径、reservation、async recovery、mClock 边界 第 11 篇 · EC 路径 ECBackend、整条带 vs RMW 上一篇:BlueStore 读路径 · 下一篇:EC 路径
版本锚定:Ceph Squid v19.2.5;
docs.ceph.com/en/squid/rados/configuration/mclock-config-ref/;docs.ceph.com/en/squid/dev/osd_internals/;源码src/osd/PrimaryLogPG.cc、src/osd/scheduler/mClockScheduler.cc@ v19.2.5。本篇不引用未经公开发表的 CBT 基准测试数字。
一、三种机制的边界
先用开发者文档坐标系对齐(docs.ceph.com/en/squid/dev/osd_internals/:log_based_pg、recovery_reservation、backfill_reservation、async_recovery、scrub):
| 维度 | Log-based Recovery | Backfill | Scrub / Deep Scrub |
|---|---|---|---|
| 文档定位 | PG log / pg_missing_t 驱动 |
log 不足以覆盖差异时的全量对象对齐 | 主动校验;非「把 PG 修回 clean」的主路径 |
| 触发 | peering 后发现 missing / divergent | 新 OSD、log truncated、扩副本等 | osd_scrub_* /
osd_deep_scrub_interval 或手动 |
| 选对象 | missing 集合(日志已知) | 双端对象列表扫描 + last_backfill 游标 |
顺序扫 PG 对象 |
| 保证 | 把 acting 成员补到日志所指向的版本 | target 最终拥有与 Primary 一致的对象集合 | 发现元数据/数据不一致;修复常需显式 repair |
| 并发 | osd_recovery_max_active 等 +
reservation |
osd_max_backfills(入站/出站分别计)+
reservation |
osd_max_scrubs;另有 scrub reservation |
| PG 状态线索 | recovery_wait / recovering /
recovery_toofull |
backfill_wait / backfilling /
backfill_toofull |
scrub 相关状态与 inconsistent 计数 |
混淆代价:把 backfill 当 recovery 去调
osd_recovery_max_active,或指望 scrub
自动完成数据修复,都会配错旋钮。
二、Recovery:日志驱动修复
2.1 触发条件
OSD 宕机后,PG 进入 peering 阶段(见第 5 篇)。Peering
完成后,PG 的状态机转入 active 状态,此时
Primary 知道哪些对象在某个 acting OSD 上有 missing(该 OSD
宕机期间写入的版本它没有)。这些 missing 对象的信息从 PG log
中读出,存入 pg_missing_t 结构。
Recovery 的起点是
pg_missing_t——这是一个以对象名为键、以”需要的版本号”为值的集合。Recovery
不需要全量扫描对象,因为 PG log
已经精确记录了哪些对象在哪个版本点产生了分歧。这是 recovery
比 backfill
效率高的根本原因:有日志则只修复已知差异,无日志则必须全量对比。
2.2 执行流程
- Primary 从
pg_missing_t按优先级(push操作优先于pull)选取对象,发起MOSDPGPush/MOSDPGPull消息给对端 OSD。 - 对端 OSD 读取对应对象(从 BlueStore)并发送
MOSDPGPushReply或数据响应。 - Primary 将收到的数据写入本地
BlueStore(一次普通的对象写),写完后更新
pg_missing_t,并通知 Replica 对端也已同步。 - 所有 missing 对象修复完成后,PG 状态转为
active+clean。
2.3 Recovery 的优先级与限速
Recovery 是有优先级的写操作:PrimaryLogPG 将 recovery ops
以 MSG_PRIO_LOW 放入 OSD 消息队列,低于客户端
I/O 的默认优先级。但”低优先级”并不意味着零带宽消耗——当单 OSD
同时有大量 PG 在 recovery 且每个 PG
都在推送数据时,恢复流量可以占满磁盘 I/O。
关键限速参数:
osd_recovery_max_active(Squid 默认:SSD 10,HDD 3):单 OSD 同时活跃的 recovery ops 上限。osd_recovery_max_chunk(默认 8 MiB):单次 push/pull 的最大对象数据量。osd_recovery_op_priority:recovery ops 在 OSD op queue 中的相对优先级(默认 3,低于客户端 I/O 的 63)。
mClock 调度器(第五节)进一步控制 recovery 在 op queue 中的带宽份额,但只对 op queue 内的调度生效,不控制 BlockDevice 层的 aio 带宽。
三、Backfill:全量对象拷贝
3.1 何时触发 Backfill
Backfill 在 PG log 无法覆盖分歧时触发:
- 新 OSD 加入,PG 的某个副本需要放到该 OSD 上(该 OSD 没有任何历史数据);
- OSD 长时间宕机,PG log 已 truncate 超过其 missing
的范围(
pg_log_max_size控制 PG log 保留长度); - 调整副本数量(如从 2 副本扩到 3 副本,新副本从零开始)。
Backfill 的目标 OSD(backfill_targets)在
peering 阶段被确定,记录在 PG 的
info.last_backfill(标记该 OSD 的 backfill
进度)。
3.2 执行流程
Backfill 的核心是双端对象列表扫描:
- Primary 扫描本地 PG 内所有对象(按 hobject_t
字典序),得到
local_objects; - 向 backfill target OSD 发送
MOSDPGScan请求,target 返回其当前对象列表; - Primary 按字典序合并比对:对象只在 Primary 有 → push 到 target;对象只在 target 有 → 从 target 删除(stale data);版本不一致 → push 正确版本。
- 每次扫描处理
osd_backfill_scan_max(默认 512)个对象,推进last_backfill游标; - 扫描完成(
last_backfill_started == hobject_t::get_max())后 backfill 完成,PG 转active+clean。
Backfill 期间,PG 处于 active+backfilling
状态,可以提供客户端 I/O(对 Primary
上已有数据的读写正常进行),但 backfill target OSD
上的数据在 backfill 完成前不参与客户端读(Primary
不会将客户端 I/O 路由到未完成 backfill 的 OSD)。
3.3 Backfill 的带宽影响
Backfill 比 recovery 更”重”:它不仅需要网络带宽传输对象数据,还需要两端各做一次顺序 I/O(Primary 读,target 写),在 HDD 集群中可以吃满磁盘顺序读带宽。
限速参数:
osd_max_backfills(默认 1):单 OSD 同时进行 backfill 的 PG 数上限。设为 1 意味着一个 OSD 一次只做一个 PG 的 backfill,是保护前台延迟的默认保守配置。osd_backfill_scan_min/osd_backfill_scan_max:控制每次扫描的对象数量,间接控制 backfill 速率。
四、Scrub 与 Deep Scrub
4.1 Shallow Scrub
Shallow scrub(简称 scrub)只比对对象的元数据:对象大小、xattr 列表、stat 信息。不读对象数据,不做 checksum 验证。
触发:PG 上次 scrub 时间超过
osd_scrub_min_interval(默认 1 天)且在
osd_scrub_max_interval(默认 7
天)内必须完成。
开销:相对小,主要是 RocksDB 读(onode/attrs),不涉及大量 BlockDevice aio 读。
4.2 Deep Scrub
Deep scrub 读取对象数据,触发 BlueStore 的 checksum 验证(见第 9 篇),并在副本间比对数据内容 hash。这是发现静默数据损坏(bit rot、固件 bug、DRAM 翻转等)的主要机制。
触发:PG 上次 deep scrub 时间超过
osd_deep_scrub_interval(默认 7
天)。也可以手动触发:ceph pg deep-scrub <pgid>。
开销:读取整个 PG 的所有对象数据,I/O 量与 PG 数据量成正比。对于存储大量写入后未读取的冷数据的 OSD,deep scrub 是唯一会读取这些数据的操作,HDD 上可能持续数小时,与前台 I/O 竞争盘带宽。
4.3 Scrub 的执行机制
Scrub 由 PrimaryLogPG 发起,通过
ScrubStore 管理对象列表推进。执行流程:
- Primary 向所有 acting OSDs 发送
MOSDRepScrub,请求各副本的对象元数据(shallow)或数据 hash(deep); - 副本 OSD 读取本地数据,返回
ScrubMap; - Primary 比对所有副本的
ScrubMap,发现不一致时标记对象,记录inconsistent objects到 PG 的 scrub 状态; - Scrub 结果通过
MOSDRepScrubMap汇总,不一致对象触发修复或告警(pg_num_inconsistent)。
发现不一致后,Ceph
不会自动修复(防止把损坏的版本传播到好的副本);需要手动执行
ceph pg repair <pgid>
触发修复,修复逻辑是从多数副本中找出”好的”版本覆盖损坏副本。
五、Reservation 机制
官方 Recovery Reservation / Backfill
Reservation(dev/osd_internals/recovery_reservation/、backfill_reservation/)把限流写成分布式槽位协议,而不是单机
sleep。
5.1 顺序与防环
- Primary 先向本机
OSDService::local_reserver申请 local 槽位; - 再按 OSD 编号顺序向远端申请 remote 槽位(log-based recovery 对 replica 的 remote 请求文档写明 总会授予,但可能排队);
- backfill 对 target 的 remote
申请可以拒绝(例如超过
backfillfull_ratio)→ Primary 释放 local、等待osd_backfill_retry_interval后重试; - 完成后
RELEASE;进程崩溃则槽位回收。
永远先 local 后 remote,且 remote 按 OSD
id
排序——防止环形等待。调试:ceph daemon osd.N dump_recovery_reservations。
5.2
osd_max_backfills 的易错点
文档明确:该上限对 outgoing 与 incoming
分别计数。因此单 OSD 上同时看到「约 (2 )
osd_max_backfills」条 backfill
相关活动并不稀奇——不是监控算错,而是语义如此。
5.3 优先级(可证伪)
Backfill Reservation
文给出优先级表(节选):普通 backfill 基线 100、degraded
backfill 140、recovery 180、inactive(低于
min_size)220,force-recovery 255
/ force-backfill 254。可用 force-*
验证「强制项是否插队」——这是运维可做的行为实验,无需压测数字。
六、mClock QoS 调度器
6.1 算法锚点(Gulati et al., OSDI 2010)
mClock 来自 Gulati、Merchant、Varman,《mClock: Handling
Throughput Variability for Hypervisor IO
Scheduling》,OSDI
2010。原问题:虚拟化环境中多客户端共享存储时,如何在
reservation(保底)/ weight(比例共享)/
limit(上限)
三维约束下调度,并应对后端吞吐波动。Ceph 从 Pacific
起把该标签化调度接到 OSD op queue,取代
WPQ;Squid 配置见 mclock-config-ref。
Ceph 侧常见三类标签(名称以源码/文档为准):
| 标签 | 典型内容 | 默认姿态(high_client_ops) |
|---|---|---|
| client | 客户端 I/O | 高 reservation,前台优先 |
| recovery | recovery / backfill 相关 op | 低 reservation,防占满 |
| best_effort | scrub、snap trim 等 | 有 limit,防饥饿也防喧宾夺主 |
用 profile(high_client_ops /
balanced /
high_recovery_ops)切换,避免手调一堆
_res/_wgt/_lim 却破坏约束。官方 QoS 对比研究见
dev/osd_internals/mclock_wpq_cmp_study/——引用其中数字时必须带上原文
CBT/硬件假设;本篇不摘录未独立复核的 IOPS
表。
6.2 控制边界:谁听 mClock,谁不听
听:进入 OSD op queue、带 mClock 标签出队的消息/操作(含多数 recovery/backfill/scrub 的调度入口)。
不听(工程间隙,可证伪):
- RocksDB compaction / flush:BlueStore 内 LSM 线程直打 BlueFS/BlockDevice,不经 op queue(第 7–9 篇)。命题:「只调低 recovery mClock 份额,compaction 毛刺必消失」→ 预期假。
- deferred_finisher 的裸盘 aio:后台把
PREFIX_DEFERRED应用到数据盘(第 8 篇),不经 mClock。 - 已在途的 BlockDevice aio:mClock 管的是提交/出队速率,不是内核/用户态块层对每个已提交 aio 的二次整形。
因此:「mClock 限制了 recovery」≠「盘利用率曲线上
recovery 字节恒低于某硬顶」。仍需
osd_max_backfills、osd_recovery_max_active、deferred/RocksDB
调优与之正交。
6.3 Profile 使用
| Profile | 意图 |
|---|---|
high_client_ops(默认) |
生产前台优先 |
high_recovery_ops |
维护窗加快恢复 |
balanced |
折中 |
ceph config set osd osd_mclock_profile high_recovery_ops
切换。切换后客户端不会「冻结」,只是 recovery 在 op queue
内更易出队;盘已饱和时 P99
仍可能差——用延迟直方图检验,不要只用 profile 名称叙事。
七、Async Recovery
官方 Asynchronous
Recovery(dev/osd_internals/async_recovery/)把问题钉成:Nautilus
之前,log-based recovery 会阻塞对 missing
对象的写,直到该对象恢复;当 PG log 很长、missing
分散时,小更新被拖成慢请求。Backfill 早已能在 acting
外补齐;async recovery 的思路是:仍用 PG log
决定补什么,但把修复放到与 live acting set
带外的路径,类似 backfill 的可用性姿态。
启用条件(文档列举,非单一布尔):
- 尽量维持
min_size可用副本; - 用 log 长度差与历史 missing 等近似代价估计;
- 与阈值
osd_async_recovery_min_cost比较——超过才走异步。
peering 选 acting 时往往只有各端 pg_info_t
边界,尚未拉齐完整 log,故代价是近似值。异步进行时,仍须把
log 条目发给 async 目标(与 backfill OSD
类似),以便追上。
权衡(可检验):
- 收益:减少「对 missing 对象的同步阻塞写」导致的慢请求;
- 代价:在
active+recovering且存在 async 目标时,双重故障窗口的解释更复杂——缺的数据可能尚未落在所有目标上; - 对照实验:同一缺失规模下,同步 vs
异步对
slow ops计数的影响(需真实集群;本篇不贴伪造输出)。
八、争论:恢复速度 vs 前台延迟
这是 Ceph 运维中最经典的权衡争论:
“快速恢复”派认为,应该提高 recovery
并发(osd_recovery_max_active、osd_max_backfills),使
PG 尽快回到 active+clean,减少 double
fault(第二个 OSD 宕机导致数据不可用)的时间窗口。在副本数为
2 的集群中,一个 OSD
宕机后另一个也出故障的概率随恢复时间线性增加。
“保护前台”派认为,在生产集群中前台客户端 I/O 的延迟稳定性优先于快速恢复;多数 OSD 宕机是短暂故障(重启、升级),等它重新上线后 recovery cost 极低,不需要激进的恢复速率;激进恢复在 HDD 集群上会直接导致客户端 P99 延迟上升数倍。
Ceph
官方文档(docs.ceph.com/en/squid/rados/troubleshooting/troubleshooting-pg/)的立场是:使用
mClock profile 在维护窗口切换,不要在生产 I/O
高峰期修改恢复限速参数。这个立场本身是工程判断,而非基于某个单一的
benchmark 结论。
九、开放问题
1. mClock 对 BlockDevice 层的控制缺口。mClock 的 op queue 调度与 aio 实际 I/O 之间的解耦,导致”设置了 recovery 带宽上限,但磁盘 I/O 仍被 recovery 占满”的现象在生产中反复出现。Ceph 社区对是否应该引入 BlueStore 层的 I/O 速率限制(类似 Linux cgroup blkio 的机制,但在用户态)有持续讨论,但主线尚未有对应实现。可读入口:Ceph dev mailing list 中关于 “bluestore io throttling” 的讨论线程。
2. Async recovery
的可观测性。ceph pg stat 会显示处于
active+recovering 状态的 PG 数量,但 recovery
的剩余字节数(pg_stat.stats.sum.num_bytes_recovered
vs 总 missing 字节)需要从 ceph pg dump 的
pg_stats 字段解析。目前没有统一的”全集群
recovery
进度百分比”视图,运维需要手动聚合计算。这是观测性的工程缺口,影响对
double fault 风险窗口的实时估计。
3. Scrub 窗口与冷数据的检测延迟。默认 7 天的 deep scrub 周期,对于写入后从未被读取的冷数据,意味着静默损坏最长 7 天才被发现。如果集群中有大量冷数据(如归档池),建议缩短 deep scrub 周期,但这与减少 deep scrub 对前台 I/O 影响的目标相矛盾。如何在”冷数据 PG 积极 scrub”和”热数据 PG 减少 scrub”之间做自适应调度,目前没有成熟的生产方案。
十、参考资料
规范 / 官方文档(A 级)
- Ceph Squid,Recovery
Reservation,
docs.ceph.com/en/squid/dev/osd_internals/recovery_reservation/。 - Ceph Squid,Backfill
Reservation,
docs.ceph.com/en/squid/dev/osd_internals/backfill_reservation/。 - Ceph Squid,Asynchronous
Recovery,
docs.ceph.com/en/squid/dev/osd_internals/async_recovery/。 - Ceph Squid,Log Based
PG,
docs.ceph.com/en/squid/dev/osd_internals/log_based_pg/。 - Ceph Squid,mClock Config
Reference,
docs.ceph.com/en/squid/rados/configuration/mclock-config-ref/。 - Ceph Squid,Troubleshooting
PGs,
docs.ceph.com/en/squid/rados/troubleshooting/troubleshooting-pg/。 - Ceph Squid,OSD Configuration
Reference(
osd_recovery_max_active、osd_max_backfills、osd_deep_scrub_interval等),docs.ceph.com/en/squid/rados/configuration/osd-config-ref/。
源码(A 级)
ceph/cephtag v19.2.5:src/osd/PrimaryLogPG.cc(scrub、recovery 逻辑);src/osd/scheduler/mClockScheduler.cc(mClock 实现);src/osd/osd_types.h(pg_missing_t、backfill_info_t)。
论文(A 级)
- Gulati, A., Merchant, A., & Varman, P. J. (2010). mClock: Handling Throughput Variability for Hypervisor IO Scheduling. OSDI 2010. mClock 算法原始论文,Ceph op queue 调度的算法基础。
站内
- 第 5 篇:PG 与 Peering(recovery 的前置:peering 状态机)
- 第 9 篇:BlueStore 读路径(deep scrub 触发的 checksum 验证)
- 第 11 篇:EC 路径 · 系列目录
→ 系列目录 · 上一篇:BlueStore 读路径 · 下一篇:EC 路径
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Ceph RADOS】RADOS 内核全景:五轴坐标系与 18 篇路线
补齐站内 distributed/38 之上的 RADOS 内核层缺口;定义 Map 传播、通信、一致性、本地存储、接入代价五条坐标系,给出 18 篇阅读路线与系列边界。
【Ceph RADOS】PG 与 Peering:PrimaryLogPG、PG log 与状态机
拆解 PG 命名与生命周期、PrimaryLogPG 的日志式复制设计、pg_log_entry_t 格式、last_epoch_started 与 last_epoch_clean 的语义差异,以及 peering 状态机从 Reset 到 Active 的完整路径。
【Ceph RADOS】排障坐标系:slow ops、peering 卡死、满盘、恢复拖死前台与 checksum 错误
按五轴映射 Ceph 常见故障症状:slow ops(慢操作)、peering 卡死、满盘/nearfull、recovery 拖死前台、checksum/inconsistent——每轴给出核对顺序与观测入口,不粘贴伪造集群输出。
【Ceph RADOS】MON/MGR 与 Map:quorum、epoch 传播与客户端可见性
拆解 Ceph MON Paxos quorum、OSDMap/PGMap 结构与 epoch 传播机制;说明 MGR 主备模型与模块框架;分析客户端 epoch 缓存与可见性窗口的边界条件。