从 Deep Scrubbing 滞后到集群性能跃迁:一次“以小见大”的 Ceph 优化实录

文章目录

    • [一、 现象:Deep Scrubbing 永远跑不完](#一、 现象:Deep Scrubbing 永远跑不完)
    • [二、 番外篇:为什么 Autoscaler "失灵"了?](#二、 番外篇:为什么 Autoscaler “失灵”了?)
      • [1. 源码揭秘:Autoscaler 的"盲区"](#1. 源码揭秘:Autoscaler 的“盲区”)
      • [2. 缺失的拼图:告诉它"我是主力"](#2. 缺失的拼图:告诉它“我是主力”)
    • [三、 中场休息:Autoscaler 的"建议"与人工的"克制"](#三、 中场休息:Autoscaler 的“建议”与人工的“克制”)
      • [1. 开启"只看不动"模式](#1. 开启“只看不动”模式)
      • [2. 查看系统建议](#2. 查看系统建议)
      • [3. 人工决策:克制的艺术](#3. 人工决策:克制的艺术)
    • [四、 决策与机制:从 128 到 512 的漫长扩容](#四、 决策与机制:从 128 到 512 的漫长扩容)
      • [为什么扩容花了整整两天?(Step-by-Step 机制)](#为什么扩容花了整整两天?(Step-by-Step 机制))
    • [五、 瓶颈:令人窒息的 30MB/s](#五、 瓶颈:令人窒息的 30MB/s)
    • [六、 破局:解锁带宽,全速愈合](#六、 破局:解锁带宽,全速愈合)
      • [1. mclock 的"保守"与"神秘爆发"](#1. mclock 的“保守”与“神秘爆发”)
      • [2. 人工介入:小步快跑,稳字当头](#2. 人工介入:小步快跑,稳字当头)
    • [七、 总结:涉及的 Ceph 技术知识点](#七、 总结:涉及的 Ceph 技术知识点)

#作者:高瑞东

在维护ceph分布式存储系统时,我们常常会从一个不起眼的报警信号出发,最终揭开系统架构层面的隐患。最近,我们在处理一起 Ceph 集群(版本17.2.5) Deep Scrubbing(深度清洗)无法按计划完成 的问题时,就经历了一次典型的"以小见大"的排查过程。

本文将复盘这次故障的解决路径:从发现 Scrubbing 积压,到定位"巨型 PG"根因,再到突破 30MB/s 的恢复速度限制,最后通过修复失效的 Autoscaler 机制,实现集群的彻底治愈。

一、 现象:Deep Scrubbing 永远跑不完

问题起因 :运维监控发现,某核心存储池的 Deep Scrubbing 任务长期处于积压状态,无法在预定的 osd_scrub_begin_hourosd_scrub_end_hour 窗口内完成。

科普:Scrubbing 为什么不能停?

Ceph 的 Scrubbing 机制(类似 RAID 的 Verify)是数据一致性的最后一道防线。

  • 检测静默损坏:硬盘会发生位翻转(Bit Rot),只有通过 Deep Scrubbing 逐字节比对副本,才能发现并修复。
  • 避免灾难叠加:如果长期不清洗,坏块会悄悄累积。一旦某天发生磁盘故障需要数据恢复,可能发现剩余的副本恰好也是"坏"的,导致数据彻底丢失。

因此,Scrubbing 积压绝非小事,它意味着集群正处于"裸奔"风险中。

初步分析

Deep Scrubbing 需要读取对象数据并进行校验,是一个高 I/O 消耗的操作。如果它跑不完,通常意味着:

  1. 磁盘性能瓶颈?(排除,磁盘负载尚可)
  2. 单次 Scrubbing 耗时过长?

深入排查

检查 PG (Placement Group) 状态后,我们发现了一个惊人的事实:该存储池的 pg_num 仅为 128,但数据量却非常大。这意味着每个 PG 承载了过多的对象(Objects)和数据量。

结论 :这是典型的 "Giant PG"(巨型 PG) 问题。

当 PG 过大时,Scrubbing 锁定 PG 的时间变长,不仅影响业务 I/O,而且导致系统无法在有限的时间窗口内清洗完如此巨大的数据块,最终导致任务积压。

二、 番外篇:为什么 Autoscaler "失灵"了?

这里有一个令人费解的细节:我们明明开启了 Autoscaler (pg_autoscale_mode = on),为什么它没有自动把 PG 加上去?

1. 源码揭秘:Autoscaler 的"盲区"

带着疑问,我们查阅了 Ceph 源码 <src/pybind/mgr/pg_autoscaler/module.py>。Autoscaler 计算理想 PG 数的核心逻辑在 _get_pool_pg_targets 函数中。

它在决定给一个存储池分配多少 PG 时,会计算一个关键指标 capacity_ratio(该池子应占总容量的比例)。

python 复制代码
# 核心逻辑简化:
# 1. 计算池子实际占用(或预设目标)
pool_raw_used = max(pool_logical_used, target_bytes) * raw_used_rate
# 2. 计算容量占比
capacity_ratio = float(pool_raw_used) / capacity
capacity_ratio = max(capacity_ratio, target_ratio)

这里暴露了问题的根源:

  • 场景还原

    • 集群规模 :54 个 OSD,单盘 9.1TB,总容量约 491TB
    • 存储池现状 :该池已使用 54TB 物理容量(含三副本,即逻辑数据量约 18TB)。
    • PG 现状 :初始仅 128 个。
  • Autoscaler 的"滞后计算"

    1. 计算容量占比54TB / 491TB ≈ 0.11 (11%)。
    2. 计算理想 PG
      • 集群目标总 PG = 54 OSD * 100 = 5400
      • 该池分到的 Raw PG = 5400 * 0.11 = 594
      • 该池的逻辑 PG (pg_num) = 594 / 3副本 = 198
      • 向下取整到 2 的幂次方 -> 128
    3. 阈值拦截(致命一击)
      • Autoscaler 计算出的理想值是 128(或者接近 256)。
      • 当前已经是 128 了。
      • Autoscaler 认为:"完美!不需要调整。"

这就导致了一个尴尬的局面:

Autoscaler 认为一切正常,但实际上每个 PG 承载了 54TB / 128 ≈ 432GB 的数据(物理容量)。

对于 Deep Scrubbing 来说,要在一个时间窗口内清洗 432GB 的数据,对 HDD 来说简直是天方夜谭。这直接导致了 Scrubbing 任务超时、积压,最终卡死。

结论 :默认的 Autoscaler 逻辑是基于"容量占比"分配 PG 的。对于**"数据量很大(54TB)但占比不高(11%)"的存储池,它分配的 PG 数(128)虽然符合均衡原则,但严重不足以支撑 Deep Scrubbing 的性能需求**。

2. 缺失的拼图:告诉它"我是主力"

为了打破这个滞后僵局,我们需要手动介入,告诉 Autoscaler:"别管现在的比例了,这个池子就是未来的主力,请按最大比例预留 PG!"

我们通过设置 target_size_ratio 来实现这一点。

从上面的代码可以看到,max(capacity_ratio, target_ratio) 会取两者中的最大值。如果我们设置 target_size_ratio = 0.9(预示该池将占用 90% 的集群空间):

  • Autoscaler 会忽略当前 33% 的占比。
  • 强制按 90% 计算:理想 PG = (5400 * 0.9) / 3 = 1620
  • 取整后得到 2048(或 1024)。
  • 这会立即触发扩容,将 PG 数量拉升到一个能让单 PG 数据量保持在合理范围(如 30-50GB)的水平。

修正命令

bash 复制代码
# 告诉 Autoscaler:这个池子是绝对主力,预计占用 90% 的容量
ceph osd pool set <pool_name> target_size_ratio 0.9

一旦设置,代码中的 max(capacity_ratio, target_ratio) 就会选中 0.9。Autoscaler 会立刻计算出:总 PG 数 * 90% = 理想 PG 数(例如 2048)。

但是,直接让 Autoscaler 自动接管一切真的安全吗?

三、 中场休息:Autoscaler 的"建议"与人工的"克制"

在查明原理后,我们面临一个关键抉择:是直接开启自动模式(mode=on)让系统放飞自我,还是采取更稳妥的方式?

考虑到集群正如火如荼地运行核心业务,如果让 Autoscaler 突然发起大规模的 PG 分裂(Split),随之而来的数据迁移(Rebalancing)可能会引发不可控的 I/O 抖动。

于是,我们采取了 "咨询模式" (pg_autoscale_mode = warn)

1. 开启"只看不动"模式

我们将 Autoscaler 设置为 warn 模式:

bash 复制代码
ceph osd pool set <pool_name> pg_autoscale_mode warn

在这个模式下,Autoscaler 会在后台利用上述公式默默计算理想的 PG 数(pg_num_final),但绝不会自动执行调整 。它只会通过 ceph health 抛出 POOL_TOO_FEW_PGS 警告,告诉我们:"我觉得现在的 PG 太少了"。

2. 查看系统建议

通过 ceph osd pool autoscale-status 命令,我们可以看到 Autoscaler 的"心理活动":

text 复制代码
POOL        SIZE  TARGET SIZE  RATE  RAW CAPACITY   RATIO  TARGET RATIO  EFFECTIVE RATIO  BIAS  PG_NUM  NEW PG_NUM  AUTOSCALE
rbd_data    50T        100T   3.0        500T   0.1000        0.2000           0.2000   1.0     128        1024       warn

可以看到,系统明确建议我们将 PG 数调整为 1024NEW PG_NUM)。

3. 人工决策:克制的艺术

虽然系统建议 1024,但我们决定分步走

直接从 128 跳到 1024 步子太大。为了保证业务平稳,我们决定先手动扩容到 512

这正是 warn 模式的价值所在:它提供了基于算法的精准计算辅助,但把最终何时执行、执行多少的"扳机"留给了管理员。

四、 决策与机制:从 128 到 512 的漫长扩容

为了彻底解决问题,我们将 pg_num 扩容到 512

为什么要扩容?(扩容的好处)

  1. 化整为零:将巨大的数据块切分为更小的单元,Scrubbing 更加轻快,不再阻塞业务。
  2. 并发提升:更多的 PG 意味着更多的数据队列和锁粒度,能更好地发挥 HDD 的并发性能。
  3. 均衡分布:更多的小 PG 能更均匀地散落在 OSD 上,避免出现"热点 OSD"。

我们执行了以下命令:

bash 复制代码
ceph osd pool set <pool> pg_num 512
ceph osd pool set <pool> pgp_num 512

为什么扩容花了整整两天?(Step-by-Step 机制)

很多人误以为执行完命令后,PG 数量会瞬间翻倍。但在我们的 HDD 集群上,这个过程持续了两天。这是 Ceph 刻意设计的保护机制。

  1. 步进牵引 (Gap Throttling)

    Ceph Manager 有一个保护参数 mgr_max_pg_num_change(默认 128)。它强制 pg_num(拆分)最多只能比 pgp_num(迁移)快 128 个。

    • pg_num 达到 pgp_num + 128 时,系统会强制暂停拆分
    • 必须等待慢速的数据迁移(pgp_num 增加)追上来,缩短差距后,pg_num 才会继续增加。
  2. 流控保护 (Misplaced Throttling)

    数据迁移的速度由 target_max_misplaced_ratio(默认 5%)控制。

    • 如果当前因为扩容导致的"位置错误"对象超过 5%,pgp_num 就会停止增加。
    • 对于 HDD 集群,数据搬运速度远慢于 SSD,导致这个"等待-追赶"的循环被拉得很长。

这种拆分-等待-迁移-再拆分的循环,虽然耗时,但避免了数千个 PG 同时产生导致的 I/O 风暴,保护了脆弱的 HDD 性能。

五、 瓶颈:令人窒息的 30MB/s

在扩容开始后,我们遇到了第二个拦路虎:恢复吞吐量卡在 30MB/s

专业分析

面对这种极度平稳的低速,我们排除了硬件瓶颈,锁定了 mclock scheduler 的 QoS 限制。

对于 HDD OSD 而言,默认的 mClock 参数过于保守,且 high_client_ops 策略极度压制了后台流量。

六、 破局:解锁带宽,全速愈合

找到症结后,我们打出了一套组合拳:

1. mclock 的"保守"与"神秘爆发"

我们尝试调整 mclock 的配置(Ceph Quincy 版本的默认 QoS 调度器),试图为数据迁移开绿灯:

bash 复制代码
ceph config set osd osd_mclock_profile high_recovery_ops

然而,结果令人沮丧。即使开启了 high_recovery_ops,Recovery 速度依然不温不火,徘徊在 70MB/s 左右。这说明 mclock 的调度逻辑在 HDD 场景下显得过于"保守",或者说它的上限被某些深层机制限制住了。

但转机出现在一个意想不到的时刻:

当我们手动将 pg_num 调整到位(比如 2048),系统开始逐步调整 pgp_num 进行实际的数据重平衡(Rebalance)时,Recovery 速度突然飙升到了 200MB/s!

观察到的现象:

  • Split 阶段(仅 pg_num 增加):速度受限,I/O 压力大,mclock 似乎在极力压制。
  • Rebalance 阶段(pgp_num 追赶) :一旦 pg_num 稳定,开始纯粹的数据移动时,系统仿佛解开了束缚。

技术真相:Split 与 Rebalance 的本质差异

带着疑惑,我们深入阅读了 Ceph BlueStore 的源码,在 src/os/bluestore/BlueStore.cc 中找到了答案。

  1. PG Split 是"元数据操作" (Metadata Only)

    我们在 _split_collection 函数中发现,PG 分裂在底层存储引擎中并不会移动物理对象

    • 原理:BlueStore 中的对象是按 Hash 存储的。PG 只是一个逻辑桶。
    • 实现 :分裂时,代码仅需更新集合(Collection)的 bits(掩码),将原本属于父 PG 的一部分 Hash 范围"划归"给子 PG。
    • 代价 :这是一系列密集的 RocksDB 事务操作(High IOPS),涉及大量的元数据更新,但几乎没有数据吞吐(Low Throughput)
    • 现象解释:这就是为什么在 Split 阶段,你会感觉"Recovery 速度(MB/s)"上不去------因为根本就没有数据在搬运!此时的 I/O 压力全部集中在 HDD 最不擅长的随机读写(RocksDB)上。
  2. Rebalance 是"数据搬运" (Data Movement)

    当 PG 分裂完成,CRUSH Map 发生变化,数据需要从原来的 OSD 迁移到新的 OSD 时,真正的 Backfill 流程才开始。

    • 原理:读取完整的对象数据 -> 网络传输 -> 写入新 OSD。
    • 代价:这是典型的顺序读写操作(High Throughput)。
    • 现象解释:HDD 擅长顺序吞吐。一旦进入这个阶段,不再受限于 RocksDB 的随机性能,带宽瞬间被跑满,于是你看到了从 70MB/s 到 200MB/s 的"神秘爆发"。

结论 :这并不是 mClock 在 Split 阶段"压制"了速度,而是Split 本身就不产生吞吐量。所谓的"慢",其实是 HDD 在艰难地处理元数据分裂的 IOPS 瓶颈;而随后的"快",才是带宽真正的释放。

2. 人工介入:小步快跑,稳字当头

针对 HDD (Rotational Media) 的物理特性,默认的 mClock 参数可能评估不准。HDD 最大的问题是随机 IOPS 低,且对大块顺序写入的成本计算可能过高。

我们进行了以下深度优化(请根据实际压测结果调整):

  • 降低 HDD 成本计算

    默认的 cost 可能过高,导致 HDD 被认为太忙。

    bash 复制代码
        # 默认 5.2,调低为 0.5,让调度器认为 HDD 处理大块数据其实"不那么累"
        ceph config set osd osd_mclock_cost_per_byte_usec_hdd 0.5
  • 提升 HDD 容量上限

    默认的 315 IOPS 比较保守。为了抵消分片(Sharding)带来的损耗计算,可以适当调大。

    bash 复制代码
        # 默认 315,激进提升至 25000(抵消分片损耗,非真实物理IOPS)
        ceph config set osd osd_mclock_max_capacity_iops_hdd 25000
  • 常规并发参数(辅助)

    适当放宽并发限制(参考 osd.yaml.in):

    • osd_max_backfills: 提升至 4
    • osd_recovery_sleep: 调整为 0

通过这套操作,我们将原本预计数天的重平衡时间缩短到了小时级别。

七、 总结:涉及的 Ceph 技术知识点

通过这次实战,我们复习并验证了以下关键技术点:

  1. Autoscaler 的触发条件

    • 仅仅开启 on 是不够的。对于空池或增长型池,必须配置 target_size_ratiotarget_size_bytes,否则 Autoscaler 就是"瞎子"。
  2. PG Sizing 的艺术

    • PG 太少 -> 巨型 PG -> 运维噩梦。
    • PG 太多 -> 元数据爆炸。
    • 保持每个 OSD 约 100 个 PG 是黄金法则。
  3. mclock QoS 调度器

    • 理解 high_client_ops vs high_recovery_ops 的区别,是掌控 Ceph 恢复速度的关键钥匙。

结语

从忽略 target_size_ratio 导致的 Autoscaler 失效,到手动扩容后的限速陷阱,每一个环节都是对 Ceph 机制的深刻拷问。希望这篇复盘能帮助大家避开这些"坑",让你的 Ceph 集群跑得更快、更稳。

相关推荐
wniuniu_3 天前
rbd镜像的锁
ceph
JNU freshman6 天前
从 Ceph 16(Pacific)到 Ceph 18(Reef):cephadm 的伸缩性演进与 cephadm agent 到底“成熟”了吗?
java·大数据·ceph
wniuniu_6 天前
概括。。。。
ceph
JNU freshman6 天前
使用 cephadm + Docker 镜像在三台服务器上部署 Ceph 集群(含网络规划与 OSD DB/WAL 分离)
服务器·ceph·docker
JNU freshman6 天前
Ceph RBD:一个镜像能否被多客户端同时映射?如何避免“多端同时写入”风险
ceph
wniuniu_7 天前
ceph16-18差异
ceph
腾讯数据架构师8 天前
cube studio 存储资源对接ceph
ceph·kubernetes·cube-studio·ai平台
哼了个哈8 天前
[Ceph 14.2.22] 使用Ceph原生命令部署单机集群
ceph
lisanmengmeng19 天前
cephfs rbd应用
linux·运维·服务器·ceph
oMcLin20 天前
如何在 Manjaro Linux 上实现高效的 Ceph 存储集群,提升大规模文件存储的冗余性与性能?
linux·运维·ceph