从 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_15 小时前
ceph的osd
java·前端·ceph
mixboot2 天前
Ceph PG 不一致问题排查与修复 scrub errors
ceph·scrub
oMcLin2 天前
如何在CentOS 7.9 服务器上配置并优化 Ceph 分布式存储集群,提升数据冗余与性能?
服务器·ceph·centos
mixboot2 天前
Ceph BlueFS 溢出修复
ceph·bluefs溢出
only火车头6 天前
升级 ceph (16.2 -> 18.2) ceph mon 启动失败
服务器·ceph
iconball9 天前
个人用云计算学习笔记 --35 Ceph 分布式存储
运维·笔记·ceph·学习·云计算
become__better9 天前
判断ceph osd 节点磁盘异常
linux·运维·ceph
2301_800050999 天前
ceph分布式存储
笔记·分布式·ceph
wniuniu_10 天前
ceph修改
网络·ceph