前言
很多 Ceph 初学者都会产生一个核心疑惑:我明明是想要给存储池新增 PG,为什么 Ceph 不直接新建空 PG,而是采用「旧 PG 分裂出新 PG」的方式?
同时大部分人无法深度理解:
- PG 扩容时,集群到底有没有搬运真实业务数据?
- 元数据在 RocksDB 中是如何变更的?是改 Key 还是改 Value?
- 新增 PG 会不会产生大量 KV 读写、影响 OSD 元数据性能?
pg_num和pgp_num底层本质区别是什么?- 线上生产手动扩容 PG 的完整流程、隐性风险与稳定操作规范。
本文结合哈希映射原理、OSD 本地存储结构、RocksDB 元数据底层操作、Ceph 官方设计思想、线上完整运维实操,一站式完整拆解 PG 分裂全链路,从理论到底层落地全覆盖,同时补充你重点追问的「KVDB 读写细节、性能损耗」核心内容。
一、基础认知:Ceph 为什么需要 PG?
Ceph 核心数据映射链路:
Object(对象)→ PG(归置组)→ OSD(磁盘节点)
对象不会直接映射到 OSD,核心原因:
对象基于 32 位哈希值寻址,哈希空间高达 42 亿级别,无法直接映射到数量有限的物理磁盘。
PG 作为中间抽象层,核心作用:
- 压缩超大哈希空间,将海量对象均匀分组管理;
- 以 PG 为最小粒度,统一管理副本、数据恢复、巡检校验、数据迁移;
- 依托 CRUSH 算法,实现整机、机架、机房维度的数据均衡与故障域隔离。
随着集群规模扩张、业务数据增长、OSD 节点增加,单 PG 承载对象过多、单 OSD 挂载 PG 负载过高,就需要手动增加 PG 数量,完成存储池精细化扩容。
二、核心设计:为什么不能直接「凭空新建空白 PG」?
正常人的直觉逻辑:PG 不够用,直接新建一批空 PG 即可。
但该方案在 Ceph 架构中完全不可行,会引发两大致命问题:
1. 数据分布极端倾斜
举例:存储池旧 pg_num = 16( 2 4 2^4 24),所有存量对象通过:
pg_id = hash(object) mod 16
固定映射到 16 个旧 PG。
如果粗暴直接改成 64 个 PG,新增 48 个 PG 完全为空,全量业务数据全部积压在原有 16 个 PG 中,出现严重冷热不均、OSD 负载分化,集群彻底失衡。
2. 读写异常 + 大规模重定向风暴
客户端依赖 OSDMap 中的 PG 规则计算对象位置。
一旦整体 Mod 基数突变,存量对象会被重新计算到完全陌生的 PG 编号,客户端无法定位原有数据,触发大量 IO 报错、PG 重定向、请求重试,直接影响线上业务稳定性。
三、PG 分裂核心原理:基于哈希高位比特拆分
Ceph 硬性约束:PG 总数必须是 2 的整数次幂,底层依托对象 32 位固定哈希值的二进制比特位做分组划分。
1. 扩容前:pg_num = 16( 2 4 2^4 24)
映射规则:只取用对象哈希值低 4 位,计算 PG 编号,全局划分为 16 个归置组。
2. 扩容后:pg_num = 64( 2 6 2^6 26)
映射规则:升级为取用对象哈希值低 6 位 。
相比旧规则,新增高 2 位比特位参与计算。
2 个二进制比特位,存在 00 / 01 / 10 / 11 四种组合,天然实现:
1 个旧 PG,均匀分裂为 4 个新 PG,保证数据均匀打散,无空 PG。
3. 分裂编号计算公式
设旧 PG 序号为 X,旧总 PG 数 = 16:
- 高位比特
00:0 * 16 + X→ 对象保留在原旧 PG - 高位比特
01:1 * 16 + X→ 分裂为同 OSD 新 PG - 高位比特
10:2 * 16 + X→ 分裂为同 OSD 新 PG - 高位比特
11:3 * 16 + X→ 分裂为同 OSD 新 PG
所有存量对象依据哈希高位自动分组,平滑归属到新 PG,全局数据分布无倾斜。
四、底层关键细节:PG 分裂到底改了什么?(重点回答你的疑问)
1、OSD 本地存储架构
- 真实数据文件:存放业务原始对象数据,占用空间大、IO 开销高;
- RocksDB KV 数据库:全量对象元数据、PG 归属索引、对象版本、omap 索引全部存在 KV 结构中。
2、核心结论
- 对象真实数据文件:完全不动、不拷贝、不迁移、不修改权限路径;
- 仅在本地 OSD 内操作 RocksDB,完成对象 PG 归属重组;
- 分裂过程只有本地 KV 读写,无任何跨节点网络流量。
3、元数据「移动」是改 Key 还是改 Value?
- Ceph RocksDB 中,KV 的 Key 天然携带 PG 唯一标识;
- 分裂时:
- 遍历旧 PG 下所有对象 KV 条目;
- 重新计算对象归属的新 PG ID;
- 删除旧 PG 前缀的 Old Key;
- 新建携带新 PG 前缀的 New Key ,Value(对象元数据、属性、指针)完全复用、无需修改;
简单来说:
改的是 RocksDB 的 Key 键名 (替换 PG 标识),
Value 存储的对象元数据、数据指针、属性内容 原封不动。
4、新增 PG 一定会产生大量 RocksDB 读写吗?
✅ 一定会
- PG 翻倍扩容 = 全量遍历当前存储池所有对象元数据;
- 大批量 Delete + Batch Put 批量写入;
- 会触发 RocksDB 压缩、版本合并、内存置换;
- 高对象数量场景下,会明显拉高 OSD CPU、磁盘元数据 IO、锁竞争;
- 直接影响 OSD 前台业务的元数据读写延迟,这是 PG 分裂隐藏的核心性能风险。
这也是生产环境禁止大跨度一次性扩容 PG、必须低峰操作的根本原因。
五、核心概念区分:pg_num 与 pgp_num(两个完全独立阶段)
1. pg_num:控制 PG 总数 → 触发【本地PG分裂】
- 作用:定义存储池最大 PG 数量;
- 行为:仅本地 OSD 完成 RocksDB Key 重构、PG 分组拆分;
- 数据范围:所有新 PG 仍然锁定在原有 OSD 节点,无跨节点数据搬迁;
- 集群状态:会抛出
pg_num > pgp_num健康告警,仅做提示,不影响业务读写。
2. pgp_num:控制可调度PG → 触发【全局数据重平衡】
- 作用:定义参与 CRUSH 调度、故障转移、数据迁移的有效 PG 数量;
- 行为:CRUSH 重新计算每个新 PG 的目标 OSD 节点;
- 数据范围:触发跨 OSD 真实数据拷贝、副本同步、旧副本删除;
- 资源消耗:占用局域网带宽、磁盘大块 IO、CPU 编码解码,是扩容最耗资源的阶段。
生产铁律
先调大 pg_num 完成分裂 → 等待集群稳定 → 再调大 pgp_num 均衡 → 最终保持二者数值完全一致
六、Ceph 手动增加 PG:完整线上实操流程
1. 扩容前置强校验(生产必做)
集群必须完全健康,禁止带告警、带降级状态扩容:
- 所有 OSD 状态
up && in; - 全量 PG 状态
active+clean; - 无 down 节点、无 stale/peered/unclean 异常 PG;
- 无 slow request、无阻塞 IO。
bash
ceph -s
ceph pg stat
ceph osd df
2. 查看存储池现有 PG 参数
bash
ceph osd pool get 池名 pg_num
ceph osd pool get 池名 pgp_num
3. 计算合理目标 PG 数
行业通用标准:
- 单块 OSD 承载 PG 合理区间:50~100 个,上限不超过 200;
- 计算结果必须向上取 2 的幂(16/32/64/128),不支持奇数、非2次幂数值。
4. 分步执行扩容命令
bash
# 第一步:修改 pg_num,触发本地 PG 分裂(改 RocksDB 元数据)
ceph osd pool set 池名 pg_num 目标值
# 等待集群分裂完成、CPU/IO 回落、告警稳定后
# 第二步:修改 pgp_num,触发全局数据重平衡
ceph osd pool set 池名 pgp_num 目标值
七、PG 扩容全流程集群变化
阶段一:pg_num 变更 → PG 分裂
- 行为:OSD 遍历本地元数据,批量修改 RocksDB 的 Key 前缀;
- 数据:真实对象数据零移动、零拷贝;
- 开销:元数据 IO、CPU 升高,持续时间取决于对象数量;
- 现状:PG 总数翻倍,但数据全部还在原 OSD,全局依然不均衡。
阶段二:pgp_num 变更 → 全局重平衡
- 行为:CRUSH 重新调度,PG 跨节点迁移、副本重建;
- 数据:真实文件跨 OSD 网络传输、落地写入;
- 开销:带宽、大容量磁盘 IO 拉满,持续时间最长;
- 现状:数据均匀分布全集群,负载打散,扩容彻底完成。
八、线上生产环境:风险总结 + 最佳实践
1、PG 分裂阶段核心风险
- 全量遍历 RocksDB,大批量删写 KV,元数据锁竞争加剧;
- OSD CPU 打满、磁盘随机 IO 飙升,前台业务延迟抖动;
- 极端场景下,RocksDB 压力过大导致 OSD 卡死、自动重启。
2、数据均衡阶段核心风险
- 后台恢复/回填抢占业务资源,带宽打满;
- 大批量数据迁移导致读写延迟翻倍、接口超时;
- 一次性扩容跨度太大,平衡任务堆积数天。
3、生产稳定操作规范
- 严格低峰操作:凌晨、周末业务低负载窗口执行;
- 阶梯式扩容:每次只翻倍(16→32→64),禁止 16→128 跨级;
- 提前限流降级:降低后台恢复并发、优先级,避免抢占业务IO;
- 关闭周期巡检:临时关闭 scrub、deep-scrub,减少资源争夺。
临时限流配置
bash
ceph tell osd.* injectargs \
'--osd_max_backfills 2 \
--osd_recovery_max_active 2 \
--osd_recovery_op_priority 1'
ceph osd set noscrub
ceph osd set nodeep-scrub
扩容完成后恢复默认配置
bash
ceph osd unset noscrub
ceph osd unset nodeep-scrub
ceph tell osd.* injectargs \
'--osd_max_backfills 4 \
--osd_recovery_max_active 4 \
--osd_recovery_op_priority 3'
九、全文总结
- Ceph 舍弃空白新建 PG,采用PG 分裂机制,核心是为了保证数据均匀分布、避免大规模 IO 异常与重定向风暴;
- 分裂底层依托哈希值高位比特拆分,保证拆分均匀、架构优雅;
- PG 分裂不碰真实数据 ,只操作 RocksDB:删除旧PG前缀Key、新建新PG前缀Key,Value完全不变;
- 新增 PG 必然产生大量 KV 读写,会影响 OSD 元数据性能,是线上不可忽视的隐性风险;
pg_num负责本地元数据分裂,pgp_num负责跨节点数据均衡,两步必须分步执行;- 生产环境 PG 扩容必须低峰、阶梯、限流操作,杜绝大跨度暴力扩容,保障集群与业务稳定。