Ceph 的 PG 数量不是“分片大小”,是一个需要在故障场景下重新计算的成本斜率
Ceph 官方文档对 PG 数量的表述有一种耐人寻味的模糊性:每个 OSD 100 个 PG 是一个“不错的起点”,但“根据集群情况可以在很大范围内变化而不产生不良影响”[reference:0]。同一份文档在另一处又给出了精确的退化信号:超过约 500 PGs/OSD 时,peering 和 RAM 用量会变得过度[reference:1]。一个既“可以变化很大”又有明确退化阈值的参数,说明它不是一条性能最优线,而是一条成本斜率——PG 数量每往上走一步,都在用 OSD 的内存、peering 耗时和恢复协调开销,换取数据分布的均匀度。正常运行时这条斜率几乎是平的;故障发生时,斜率突然变陡,而那个“100 的起点”不再是一个安全的默认值。
PG 是间接层,目的是摊薄每对象的元数据,不是存储容器
Ceph 引入 PG 的原始动机在开发者文档里写得很直白:对象先映射到 PG,PG 再映射到 OSD,这个间接层的目的是把对象分组,从而减少需要逐对象追踪的元数据和需要运行的处理过程——逐对象追踪放置历史将是“昂贵到不可行的”[reference:2]。
这个目的决定了 PG 的本质:它不表示固定大小的存储,而是“你 OSD 上恰好拥有的存储的 1/pg_num”[reference:3]。PG 数量越多,CRUSH 有更多的桶可以把对象分散开,单个 OSD 上的负载方差越小;但每一个 PG 在承载它的每一个 OSD 上,都附带一组固定开销:PG log、peering 状态、恢复追踪结构、BlueStore 元数据,这些开销相对于该 OSD 参与的 PG 数量大致线性增长[reference:4]。
所以“增加 PG 数量改善分布”这句话的完整形式是:在 OSD 的内存和 CPU 还有余量吸收新增 PG 的固定开销的前提下,增加 PG 数量改善分布。收益在数据分布一侧,成本在每一个 OSD 的运行时资源一侧,两者的比值不是常数。
100 到 200 是正常运行的舒适带,但它假设了 OSD 数量不变
Netdata 的排障指南把目标区间定在 100–200 PGs/OSD,并解释了这个区间两侧的代价:低于 100,CRUSH 的桶太少,小池中的数据会集中在少数 OSD 上形成热点,OSD 故障后的再平衡粒度也更粗,因为每次 PG 迁移搬动的数据更多;高于 200,每 PG 开销开始占主导,拓扑变化后的 peering 因为需要协商状态的 PG 更多而变慢,PG log 和元数据的内存消耗上升,OSD 故障后的恢复涉及更多的 PG 级协调[reference:5]。
这个区间的关键限定词是“正常运行”。它假设 OSD 数量保持稳定,每个 OSD 承担的 PG 数只由池配置和数据分布决定。但 Ceph 的 PG 数量与 OSD 数量之间是一个分母关系:总 PG 数不变,OSD 少了,每个幸存 OSD 上的 PG 数就上升。一个在 10 个 OSD 上跑在 220 PGs/OSD 的集群,一个 OSD 故障后,幸存者的 PG 数会跳到约 244;小集群上丢掉两个 OSD,就足以越过 250 的警告线,而没有创建任何新池、没有增加任何 pg_num[reference:6]。
小集群的处境更极端。3 个 OSD、3 副本的配置下,每个 PG 都落在每一个 OSD 上。一个 256 PG 的池意味着三个 OSD 各承担 256 个 PG;再加一个同样大小的池,就是 512 PGs/OSD,在正常运行状态下就已经越过了警告阈值[reference:7]。100–200 的舒适带是从“OSD 数量足够多、单个 OSD 故障不会显著改变分母”这个前提推导出来的;小集群的 PG 配置如果照搬大集群的“每 OSD 100”公式,会在故障场景下直接进入警告区间。
250 和 750 是护栏,不是性能目标
Ceph 用两道阈值防止 PG 数量失控。mon_max_pg_per_osd 默认 250,当一个 OSD 接近这个数字时,集群发出 TOO_MANY_PGS 并阻止创建新池、增加 pg_num、修改副本因子;现有集群继续服务 I/O,只阻止会增加 PG 的拓扑变更[reference:8]。更硬的一道是 osd_max_pg_per_osd_hard_ratio 默认 3.0,即 750 PGs/OSD;达到这个值时,单个 OSD 拒绝实例化任何新 PG,PG 可能卡在 creating 或 peering 状态,因为该 OSD 无法参与协商[reference:9]。
官方对这两道阈值的定性是精确的:“这是护栏,不是性能目标” [reference:10]。250 不代表“250 以内性能就没问题”,只代表“超过 250 时系统会阻止你继续往这个方向走”。一个停留在 240 PGs/OSD 的集群,peering 速度和恢复协调开销已经处于 100 PGs/OSD 时的两倍以上量级;护栏没有触发,不等于成本斜率是平的。
当警告是因为 OSD 丢失而非池创建触发时,正确的动作是恢复丢失的 OSD 或增加新的 OSD,而不是调高 mon_max_pg_per_osd 来压制警告[reference:11]。调高阈值不改变幸存 OSD 上的 PG 数,只改变系统对同一个数字的反应方式;它把“系统在警告你成本斜率已经变陡”替换成“系统不再告诉你”。
autoscaler 的 3 倍迟滞:保守是一种特性,也是一种盲区
PG autoscaler 默认以 mon_target_pg_per_osd = 100 为目标,但它只有在计算值与当前 pg_num 相差超过 3 倍时才会行动,这个 3.0 的阈值被社区描述为迟滞(hysteresis)[reference:12]。小池上这个迟滞会制造一种可观测的振荡:一个 3 GiB 的索引池,PG merge/split 只需几分钟,调整后健康警告会在 POOL_TOO_MANY_PGS 和 POOL_TOO_FEW_PGS 之间来回翻转[reference:13]。
迟滞的设计意图是避免频繁分裂/合并带来的 I/O 扰动,它的代价是pool 可以在显著偏离目标的情况下长期不被纠正。一个实际需要 200 PGs 的池停留在 64 或 512,autoscaler 都不会干预,因为它判断“差异没有大到值得动”。对于稳定负载,这是可接受的;对于数据量正在快速变化的池,这个保守阈值意味着PG 数量会以 3 倍为台阶跳跃式调整,而不是平滑地跟随数据增长。
autoscaler 还有一类结构性的不覆盖区域。带有重叠 CRUSH root 的池(例如同时跨越 SSD 和 HDD device class)会让 autoscaler 拒绝扩缩并发出警告,需要手动管理 PG 数量[reference:14]。pg_autoscale_mode='off' 的池在旧版本中会错误地消耗 PG budget:autoscaler 为它们计算 final_pg_target 并占用全局 PG 预算,尽管它们根本不能被调整,导致 on 模式的池拿到的分裂空间小于应有值[reference:15]。这类池的存在意味着“把 autoscaler 打开就没事了”这个判断,在 multi-tenant 或混合介质集群上不成立。
分裂和合并本身是一次 I/O 事件,不是元数据操作
即使 autoscaler 做出了正确的决定,执行这个决定也不是免费的。PG 分裂时,新 PG 必须 peering 并激活,在这个窗口内受影响的 PG 暂时不可用于 I/O;在繁忙集群上,一次大的 autoscaler 触发的分裂可以造成可见的延迟尖峰[reference:16]。合并时,到 PG 的 I/O 会被暂停,且一次只合并一个 PG 以限制对集群性能的影响[reference:17]。
社区邮件列表对分裂的定性更直接:“分裂 PG 是你能对集群做的最密集、最具破坏性的事情之一” [reference:18]。这种破坏性不限于分裂动作本身:PG 进入 degraded 状态后,客户端对 degraded 对象的写入会触发恢复工作流尝试恢复该对象,即使在 norecover 标志被设置的情况下也是如此[reference:19]。autoscaler 以 step 方式逐步调整 pg_num 来摊销这个成本,但“逐步”只是把一次大尖峰拆成若干次小尖峰,不改变分裂本身需要占用恢复带宽这个事实。
故障条件下,内存不是线性增长,是“气球化”
PG 数量的内存成本在正常运行时看起来可控,但多位 Ceph 开发者和运维者在邮件列表中反复强调了一个词:“balloons in failure conditions”——在故障条件下会气球化。
机制是具体的。osd_memory_target 是 BlueStore onode cache 和 RocksDB block cache 的软限制,Ceph 文档承认 RSS 可以超出目标约 20%,而 PG log 膨胀、RocksDB compaction 或大量 PG 同时 peering 可以把 RSS 推得更高 [reference:21]。当目标加上超调撞上主机 RAM 预算或容器 cgroup 限制时,OOM killer 触发,OSD 被杀,集群侧看起来像一次磁盘故障或心跳超时[reference:22]。
故障场景恰好是这三个推高因素同时激活的时刻:一个 OSD 丢失,幸存 OSD 上的 PG 数上升,大量 PG 同时 peering,PG log 因为恢复操作而增长。正常运行时每个 PG 的固定开销或许只是几 MB,但故障时幸存 OSD 承载的 PG 数增加、每个 PG 的 log 和 peering 状态同时膨胀,两者的乘积决定了 RSS 的峰值。osd_memory_target 的默认值 4 GiB 和“每 OSD 至少 1.5 倍 target 加 2 GB 系统内存”的经验规则,本质上是在为这个乘积留出故障场景下的余量;余量不足时,恢复过程本身会触发 OOM,OSD 重启,恢复重新开始,形成一个内存驱动的恢复振荡[reference:23]。
边界不在“100 PGs/OSD”这个数字里
把 PG 的间接层本质、故障时的分母收缩、autoscaler 的迟滞、分裂的 I/O 成本和内存的气球化放在一起,Ceph 的 PG 数量呈现出一个与“分片大小”直觉不同的结构:正常运行时,100–200 PGs/OSD 是一条成本斜率平缓的舒适带;故障发生时,分母缩小、peering 集中、PG log 膨胀,同一条斜率突然变陡,而护栏(250/750)只阻止你继续往陡的方向走,不告诉你当前的位置已经有多陡。
判断一个 Ceph 集群的 PG 配置是否“合适”,比看 ceph osd df 里 PGs 列的绝对值更有用的,是先确认三件事:在预期的最坏 OSD 丢失数量下,幸存 OSD 上的 PG 数是否会越过 250 的警告线;osd_memory_target 和主机 RAM 之间留出的余量,是否足以吸收故障时 PG log 和 peering 同时膨胀带来的 RSS 超调;以及 autoscaler 的 3 倍迟滞在当前的池数量和 CRUSH root 结构下,是否会在故障窗口内保持沉默而不是及时收缩。 承诺的兑现条件,写在故障场景下的分母和内存余量里,而不是写在“每 OSD 100”那个起点里。
- 点赞
- 收藏
- 关注作者
评论(0)