土法炼钢 · 系统与基础设施

【Ceph RADOS】Recovery、Backfill 与 Scrub:修复路径、reservation 机制与 mClock QoS 边界

文章导航

分类入口
storagedistributed
标签入口
#ceph#recovery#backfill#scrub#deep-scrub#mclock#reservation#async-recovery#pg#squid#v19.2.5

目录

“恢复把前台拖死了”是 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.5docs.ceph.com/en/squid/rados/configuration/mclock-config-ref/docs.ceph.com/en/squid/dev/osd_internals/;源码 src/osd/PrimaryLogPG.ccsrc/osd/scheduler/mClockScheduler.cc @ v19.2.5。本篇不引用未经公开发表的 CBT 基准测试数字。


一、三种机制的边界

先用开发者文档坐标系对齐(docs.ceph.com/en/squid/dev/osd_internals/log_based_pgrecovery_reservationbackfill_reservationasync_recoveryscrub):

维度 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 执行流程

  1. Primary 从 pg_missing_t 按优先级(push 操作优先于 pull)选取对象,发起 MOSDPGPush / MOSDPGPull 消息给对端 OSD。
  2. 对端 OSD 读取对应对象(从 BlueStore)并发送 MOSDPGPushReply 或数据响应。
  3. Primary 将收到的数据写入本地 BlueStore(一次普通的对象写),写完后更新 pg_missing_t,并通知 Replica 对端也已同步。
  4. 所有 missing 对象修复完成后,PG 状态转为 active+clean

2.3 Recovery 的优先级与限速

Recovery 是有优先级的写操作:PrimaryLogPG 将 recovery ops 以 MSG_PRIO_LOW 放入 OSD 消息队列,低于客户端 I/O 的默认优先级。但”低优先级”并不意味着零带宽消耗——当单 OSD 同时有大量 PG 在 recovery 且每个 PG 都在推送数据时,恢复流量可以占满磁盘 I/O。

关键限速参数:

mClock 调度器(第五节)进一步控制 recovery 在 op queue 中的带宽份额,但只对 op queue 内的调度生效,不控制 BlockDevice 层的 aio 带宽。


三、Backfill:全量对象拷贝

3.1 何时触发 Backfill

Backfill 在 PG log 无法覆盖分歧时触发:

Backfill 的目标 OSD(backfill_targets)在 peering 阶段被确定,记录在 PG 的 info.last_backfill(标记该 OSD 的 backfill 进度)。

3.2 执行流程

Backfill 的核心是双端对象列表扫描

  1. Primary 扫描本地 PG 内所有对象(按 hobject_t 字典序),得到 local_objects
  2. 向 backfill target OSD 发送 MOSDPGScan 请求,target 返回其当前对象列表;
  3. Primary 按字典序合并比对:对象只在 Primary 有 → push 到 target;对象只在 target 有 → 从 target 删除(stale data);版本不一致 → push 正确版本。
  4. 每次扫描处理 osd_backfill_scan_max(默认 512)个对象,推进 last_backfill 游标;
  5. 扫描完成(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 集群中可以吃满磁盘顺序读带宽。

限速参数:


四、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 管理对象列表推进。执行流程:

  1. Primary 向所有 acting OSDs 发送 MOSDRepScrub,请求各副本的对象元数据(shallow)或数据 hash(deep);
  2. 副本 OSD 读取本地数据,返回 ScrubMap
  3. Primary 比对所有副本的 ScrubMap,发现不一致时标记对象,记录 inconsistent objects 到 PG 的 scrub 状态;
  4. Scrub 结果通过 MOSDRepScrubMap 汇总,不一致对象触发修复或告警(pg_num_inconsistent)。

发现不一致后,Ceph 不会自动修复(防止把损坏的版本传播到好的副本);需要手动执行 ceph pg repair <pgid> 触发修复,修复逻辑是从多数副本中找出”好的”版本覆盖损坏副本。


五、Reservation 机制

官方 Recovery Reservation / Backfill Reservationdev/osd_internals/recovery_reservation/backfill_reservation/)把限流写成分布式槽位协议,而不是单机 sleep

5.1 顺序与防环

  1. Primary 先向本机 OSDService::local_reserver 申请 local 槽位;
  2. 再按 OSD 编号顺序向远端申请 remote 槽位(log-based recovery 对 replica 的 remote 请求文档写明 总会授予,但可能排队);
  3. backfill 对 target 的 remote 申请可以拒绝(例如超过 backfillfull_ratio)→ Primary 释放 local、等待 osd_backfill_retry_interval 后重试;
  4. 完成后 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 的调度入口)。

不听(工程间隙,可证伪)

  1. RocksDB compaction / flush:BlueStore 内 LSM 线程直打 BlueFS/BlockDevice,不经 op queue(第 7–9 篇)。命题:「只调低 recovery mClock 份额,compaction 毛刺必消失」→ 预期假。
  2. deferred_finisher 的裸盘 aio:后台把 PREFIX_DEFERRED 应用到数据盘(第 8 篇),不经 mClock。
  3. 已在途的 BlockDevice aio:mClock 管的是提交/出队速率,不是内核/用户态块层对每个已提交 aio 的二次整形。

因此:「mClock 限制了 recovery」≠「盘利用率曲线上 recovery 字节恒低于某硬顶」。仍需 osd_max_backfillsosd_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 Recoverydev/osd_internals/async_recovery/)把问题钉成:Nautilus 之前,log-based recovery 会阻塞对 missing 对象的写,直到该对象恢复;当 PG log 很长、missing 分散时,小更新被拖成慢请求。Backfill 早已能在 acting 外补齐;async recovery 的思路是:仍用 PG log 决定补什么,但把修复放到与 live acting set 带外的路径,类似 backfill 的可用性姿态。

启用条件(文档列举,非单一布尔):

peering 选 acting 时往往只有各端 pg_info_t 边界,尚未拉齐完整 log,故代价是近似值。异步进行时,仍须把 log 条目发给 async 目标(与 backfill OSD 类似),以便追上。

权衡(可检验):


八、争论:恢复速度 vs 前台延迟

这是 Ceph 运维中最经典的权衡争论:

“快速恢复”派认为,应该提高 recovery 并发(osd_recovery_max_activeosd_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 dumppg_stats 字段解析。目前没有统一的”全集群 recovery 进度百分比”视图,运维需要手动聚合计算。这是观测性的工程缺口,影响对 double fault 风险窗口的实时估计。

3. Scrub 窗口与冷数据的检测延迟。默认 7 天的 deep scrub 周期,对于写入后从未被读取的冷数据,意味着静默损坏最长 7 天才被发现。如果集群中有大量冷数据(如归档池),建议缩短 deep scrub 周期,但这与减少 deep scrub 对前台 I/O 影响的目标相矛盾。如何在”冷数据 PG 积极 scrub”和”热数据 PG 减少 scrub”之间做自适应调度,目前没有成熟的生产方案。


十、参考资料

规范 / 官方文档(A 级)

源码(A 级)

论文(A 级)

站内


系列目录 · 上一篇:BlueStore 读路径 · 下一篇:EC 路径

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .