Ceph OSD 内部机制解析:PGPool 与已删除快照(removed snap)追踪及异步裁剪
发布时间:2026/9/22 19:07:20 锦皓数字建站
追踪及异步裁剪`)
Ceph OSD 内部机制解析PGPool 与已删除快照removed snap追踪及异步裁剪【免费下载链接】cephCeph is a distributed object, block, and file storage platform项目地址: https://gitcode.com/gh_mirrors/ce/ceph导读本文深入解析 Ceph OSD 中PGPool这一核心数据结构它如何代表一个存储池的动态快照视图如何在每个 OSDMap 更新时追踪被删除的快照removed snaps以及 PG 激活后如何基于这些信息构建快照裁剪队列snap trim queue并由后台队列异步完成对象级裁剪。读完本文你将掌握从 OSDMap 更新到快照数据最终回收的完整调用链并了解当前仓库中PGPool、OSDMap、PG三者之间的协作关系。全文以 doc/dev/osd_internals/pgpool.rst 为主线结合 src/osd/PeeringState.h、src/osd/PeeringState.cc、src/osd/OSD.cc 等源码逐一印证。一、PGPool存储池的活快照视图1.1 PGPool 的定位在 Ceph 中快照删除并不是瞬间完成的当客户端如 RBD删除某个快照后OSD 需要在一段时间内持续追踪该快照已被删除这一状态直到所有相关对象头中的快照引用被裁剪完毕。PGPool正是 OSD 内部用于管理并更新已删除快照状态的结构体。根据 doc/dev/osd_internals/pgpool.rst 的说明PGPool 通过维护两个核心字段来完成这一任务cached_removed_snaps当前已删除快照集合current removed snap set是 OSD 已经观察到并缓存的全部待裁剪快照newly_removed_snaps在最近一个 epoch 中新增删除的快照newly removed snaps in the last epoch用于区分增量变化。这种全集 增量的双字段设计使得 OSD 在收到连续 OSDMap 时只需处理增量部分即可更新本地状态避免重复扫描。1.2 当前仓库中的结构体定义从当前源码看PGPool定义在 src/osd/PeeringState.hstruct PGPool { epoch_t cached_epoch; // 缓存的 osdmap epoch int64_t id; // pool id std::string name; // pool 名称 pg_pool_t info; // 池的完整信息含 removed_snaps 等 SnapContext snapc; // 默认 pool snapc开箱即用 PGPool(OSDMapRef map, int64_t i, const pg_pool_t info, const std::string name) : cached_epoch(map-get_epoch()), id(i), name(name), info(info) { snapc info.get_snap_context(); } void update(OSDMapRef map); ... };需要说明的是文档中提到的cached_removed_snaps/newly_removed_snaps两个字段名在演进后的当前代码中已被拆分为不同模块承载详见第四节但PGPool承担缓存池快照相关状态、随 map 增量更新的职责没有变化它保留了pg_pool_t info其中含removed_snaps区间集合和默认SnapContext snapc并记录cached_epoch以判断 map 是否连续。此外PGPool还提供get_readable_interval()用于基于READ_LEASE_INTERVAL池选项或osd_heartbeat_grace与osd_pool_default_read_lease_ratio配置计算读租约时长这说明 PGPool 不仅是快照追踪器也是 PG 运行时读取池级配置的便捷入口。二、初始化路径从文件存储恢复 OSDMap 到 PGPool 诞生2.1 OSD::load_pgs 从文件存储恢复 PG文档指出在OSD::load_pgs中OSD 从 PG 的 file store 中恢复 osd map并传递给_get_pool在其中用该 map 初始化 PGPool 对象。对应实现位于 src/osd/OSD.ccvoid OSD::load_pgs() { ... vectorcoll_t ls; int r store-list_collections(ls); ... for (vectorcoll_t::iterator it ls.begin(); it ! ls.end(); it) { spg_t pgid; ... epoch_t map_epoch 0; int r PG::peek_map_epoch(store.get(), pgid, map_epoch); ... if (map_epoch 0) { OSDMapRef pgosdmap service.try_get_map(map_epoch); ... } ... } }流程要点store-list_collections()枚举对象存储中的所有 collection每个 PG 对应一个 collection跳过临时 collectionis_temp以及带有删除标记_has_removal_flag的 PG后者直接执行recursive_remove_collection清理残留对每个合法 PG通过PG::peek_map_epoch从 PG 元数据中读出其创建/最后生效的map_epoch再调用service.try_get_map(map_epoch)恢复对应 epoch 的 OSDMap若池在当前 map 中已不存在则跳过该 PG对应历史 bug 10617 的场景可后续用ceph-objectstore-tool清理。2.2 _make_pg 中构造 PGPool从当前源码看文档所提到的_get_pool角色由OSD::_make_pg承担src/osd/OSD.ccPG* OSD::_make_pg(OSDMapRef createmap, spg_t pgid) { pg_pool_t pi; string name; if (createmap-have_pg_pool(pgid.pool())) { pi *createmap-get_pg_pool(pgid.pool()); name createmap-get_pool_name(pgid.pool()); ... } else { // pool 已删除从磁盘读取 final_pool_info tombstone ghobject_t oid make_final_pool_info_oid(pgid.pool()); ... } PGPool pool(createmap, pgid.pool(), pi, name); PG *pg; if (pi.type pg_pool_t::TYPE_REPLICATED || pi.type pg_pool_t::TYPE_ERASURE) pg new PrimaryLogPG(service, createmap, pool, ec_profile, pgid, ...); ... }可以看到正常情况下pg_pool_t pi和池名称直接取自createmap恢复出的 OSDMap若池已被删除则从对象存储中读取final_pool_infotombstone 对象make_final_pool_info_oid解码出池的最终pg_pool_t、名称和纠删码 profile —— 这是 OSD 重启后仍能正确处理已删除池中 PG 的关键PGPool构造函数中snapc info.get_snap_context()即基于池内已删除快照集合生成默认快照上下文最终 PGPool 被传给PrimaryLogPG成为 PG 生命周期内读取池状态的主要渠道。三、随 map 更新PGPool::update 的增量刷新逻辑文档强调每收到一个新 map就调用PGPool::update处理该 map在此函数中构建新增删除快照列表并合并到缓存集合且包含仅在发生变化或出现 map 缺口map gap时才更新的检查。对应实现在 src/osd/PeeringState.ccvoid PGPool::update(OSDMapRef map) { const pg_pool_t *pi map-get_pg_pool(id); if (!pi) { return; // pool has been deleted } info *pi; name map-get_pool_name(id); bool updated false; if ((map-get_epoch() ! cached_epoch 1) || (pi-get_snap_epoch() map-get_epoch())) { updated true; } if (info.is_pool_snaps_mode() updated) { snapc pi-get_snap_context(); } cached_epoch map-get_epoch(); }逐行解读其中的工程细节池已删除的保护map-get_pg_pool(id)返回空则直接返回避免对不存在的池做无意义更新完整刷新 info 与 name每次更新都会用最新pg_pool_t覆盖本地info其中包含快照相关的snap_epoch与removed_snaps见第四节updated判定条件对应文档所说的发生变化或 map 缺口map-get_epoch() ! cached_epoch 1新 map 与上次缓存的 epoch 不连续说明存在 map gapOSD 可能漏收了若干中间 map此时快照状态可能发生跳跃式变化必须重新同步pi-get_snap_epoch() map-get_epoch()池的snap_epoch恰好等于当前 map epoch说明本 epoch 内发生了快照创建/删除事件mon 在修改池快照时推进snap_epoch并生成新 map属于确实发生变化的典型场景仅池快照模式才刷新 snapcis_pool_snaps_mode()为真池级快照模式下removed_snaps非空且需要更新时才重新从pg_pool_t提取SnapContext避免不必要的上下文重建最后更新cached_epoch为下一轮连续性判断做准备。这段逻辑与文档描述一一对应build_removed_snaps式构建新增删除快照的职责在当前实现中等价于基于新的pg_pool_t::removed_snaps与snap_epoch判断并刷新检查条件正是文档提到的仅当发生变化或有 map gap 时。四、removed snaps 的状态承载从文档字段到当前源码映射文档描述的两个字段cached_removed_snaps、newly_removed_snaps和pg_pool_t::build_removed_snaps函数代表了该机制早期版本的设计。在当前仓库中这一职责被拆分并演进到多个数据结构理解这种映射有助于阅读代码4.1 pg_pool_t池级快照状态的源头pg_pool_t定义于 src/osd/osd_types.hepoch_t snap_epoch 0; /// osdmap epoch of last snap interval_setsnapid_t removed_snaps;snap_epoch最近一次快照变更所在的 osdmap epochget_snap_epoch()/set_snap_epoch()提供访问器见 src/osd/osd_types.hremoved_snaps以interval_setsnapid_t区间集合形式记录的已删除快照 ID是快照裁剪的总账is_pool_snaps_mode()src/osd/osd_types.h判定池是否处于池快照模式removed_snaps为空则表示非池快照池反之则是。interval_set将连续的快照 ID 合并为区间极大压缩了大规模快照删除场景下的内存占用这是 Ceph 快照管理的基础数据结构。4.2 OSDMap增量传播的载体当前实现中新删除的快照由 OSDMap 直接携带并在 map 传播过程中分发src/osd/OSDMap.h 与 src/osd/OSDMap.hmempool::osdmap::mapint64_t, snap_interval_set_t new_removed_snaps; mempool::osdmap::mapint64_t, snap_interval_set_t new_purged_snaps; ... mempool::osdmap::mapint64_t, snap_interval_set_t removed_snaps_queue;new_removed_snaps按 pool id 索引本 map 中新被删除的快照区间 —— 对应文档中的newly_removed_snapsnew_purged_snaps本 map 中新被确认已裁剪完毕的快照区间removed_snaps_queueOSDMap 内部用于在增量 map 之间推进的队列状态访问接口get_new_removed_snaps()、get_new_purged_snaps()、get_removed_snaps_queue()、in_removed_snaps_queue()位于 src/osd/OSDMap.h。换句话说文档中合并 newly_removed_snaps 到 cached_removed_snaps的聚合动作在当前代码中表现为mon 将池快照删除写入 map 的new_removed_snaps各 OSD 收到新 map 后在 PG 层面与本地snap_trimq合并见第五节。五、PG 激活从缓存集合到 snap trim queue5.1 on_activate 初始化裁剪队列文档指出激活 PG 时从cached_removed_snaps初始化快照裁剪队列减去已经裁剪过的purged_snaps剩下的就是需要裁剪的快照列表。对应实现为 src/osd/PG.cc 的PG::on_activatevoid PG::on_activate(interval_setsnapid_t snaps) { ceph_assert(!m_scrubber-are_callbacks_pending()); ceph_assert(callbacks_for_degraded_object.empty()); snap_trimq snaps; release_pg_backoffs(); projected_last_update info.last_update; }snap_trimq快照裁剪队列定义于 src/osd/PG.hinterval_setsnapid_t snap_trimq; std::setsnapid_t snap_trimq_repeat;snap_trimq是需要裁剪的快照区间集合snap_trimq_repeat记录需要重试的快照如裁剪失败或对象仍被引用时重新入队见PG::queue_snap_retrimsrc/osd/PG.cc。减去 purged_snaps中的purged_snaps定义在pg_info_tsrc/osd/osd_types.hinterval_setsnapid_t purged_snaps; /// recently removed snaps that weve purged即最近已裁剪完毕的快照记录于 PG 的持久化 info 中激活时与候选集合求差保证已完成的裁剪不会重复执行。5.2 新 map 到达时的增量合并PG::on_active_advmapsrc/osd/PG.cc负责在 PG 处于 active 状态收到新 OSDMap 时处理增量快照状态const auto new_removed_snaps osdmap-get_new_removed_snaps(); auto i new_removed_snaps.find(get_pgid().pool()); if (i ! new_removed_snaps.end()) { ... for (auto j : i-second) { if (snap_trimq.intersects(j.first, j.second)) { ... // 重复区间处理记录 overlap 并 union 进 snap_trimq } else { snap_trimq.insert(j.first, j.second); // 新增删除快照并入裁剪队列 } } ... ceph_assert(!bad || !cct-_conf-osd_debug_verify_cached_snaps); }同样地new_purged_snaps分支会反向更新info.purged_snaps若本地purged_snaps尚未包含某段已确认裁剪区间则按需修正adjust_purged_snaps否则将其从本地集合中擦除以推进状态。这里对应着文档合并 newly_removed_snaps 到 cached_removed_snaps的运行时行为且该函数还附带了osd_debug_verify_cached_snaps调试开关可在开发/测试阶段校验缓存快照状态的一致性。代码注释中还揭示了purged_snaps传播的一个已知特性向purged_snaps追加内容不增加 PG version、也不写 PG log因此在 PG 映射变化换主等场景下新主节点可能短暂缺失部分purged_snaps记录需要靠上述修正逻辑兜底。5.3 进入 clean 时触发裁剪PG::on_cleansrc/osd/PG.cc在 PG 达到 clean 状态时调用kick_snap_trim()启动一轮快照裁剪PG::on_active_actmapsrc/osd/PG.cc在 map 激活且 PG 处于 active 时同样会kick_snap_trim()。这保证了裁剪只会在 PG 稳定后异步进行不干扰正常 IO。六、异步裁剪snap_trim_wq 与现代调度器文档最后指出裁剪由snap_trim_wq异步执行。在当前的调度框架下这一职责由OSDService::queue_for_snap_trim完成src/osd/OSD.ccvoid OSDService::queue_for_snap_trim(PG *pg, uint64_t cost_per_object) { uint64_t cost_for_queue [this, cost_per_object] { if (cct-_conf-osd_op_queue mclock_scheduler) { return cost_per_object * cct-_conf-osd_pg_max_concurrent_snap_trims; } else { return cct-_conf-osd_snap_trim_cost; // 传统 WeightedPriorityQueue 行为 } }(); enqueue_back( OpSchedulerItem( unique_ptrOpSchedulerItem::OpQueueable( new PGSnapTrim(pg-get_pgid(), pg-get_osdmap_epoch())), cost_for_queue, cct-_conf-osd_snap_trim_priority, ceph_clock_now(), 0, pg-get_osdmap_epoch())); }关键点裁剪任务以PGSnapTrim操作的形式进入 OSD 的统一操作调度器OpScheduler与普通客户端 IO 一起按优先级和成本竞争执行调度成本cost计算随调度器类型不同而不同使用mclock_scheduler时按单对象成本 × 单 PG 最大并发裁剪数估算并注释说明了最后两轮迭代的成本偏差情形使用传统WeightedPriorityQueue时退化为固定osd_snap_trim_cost注释标注该分支计划在后续版本移除相关配置参数包括osd_snap_trim_priority裁剪任务的调度优先级osd_snap_trim_cost传统调度器下的裁剪成本osd_pg_max_concurrent_snap_trims每个 PG 可并发执行的裁剪对象数osd_snap_trim_sleep/osd_snap_trim_sleep_hdd/osd_snap_trim_sleep_ssd/osd_snap_trim_sleep_hybrid按后端设备类型设置的裁剪节流睡眠时间由OSD::get_osd_snap_trim_sleep()src/osd/OSD.cc根据存储类型选择用于在裁剪风暴时保护正常 IO 时延osd_debug_verify_cached_snaps调试用校验缓存快照状态一致性。OSD 还会周期性统计各 PG 待裁剪队列长度OSDService::calc_snap_trim_queue_total()src/osd/OSD.cc遍历 PG 并累加PG::get_snap_trimq_size()src/osd/PG.h供监控与状态上报使用。至此一条完整的链路闭环如下mon 删除快照 → 池 snap_epoch 推进、removed_snaps 更新 → 新 OSDMap 携带 new_removed_snaps / new_purged_snaps 广播 → OSD 收到 mapPGPool::update 刷新池信息含 gap 与 snap_epoch 检查 → PG::on_active_advmap 将 new_removed_snaps 并入 snap_trimq → PG clean/激活时 kick_snap_trim → OSDService::queue_for_snap_trim 以 PGSnapTrim 入调度器 → 后台异步裁剪对象快照引用推进 info.purged_snaps → 结果随后续 map 的 new_purged_snaps 回传形成闭环七、小结与源码索引PGPool是 OSD 管理池级快照状态的枢纽它在 OSD 启动时load_pgs→_make_pg随恢复出的 OSDMap 初始化在每个新 map 到达时通过PGPool::update以仅当变化或有 map gap的判定完成增量刷新快照删除的全集 增量状态在演进后由pg_pool_t::removed_snaps总账、OSDMap 的new_removed_snaps/new_purged_snaps增量传播与 PG 的snap_trimq/purged_snaps运行时队列与进度共同承载最终裁剪由调度器中的PGSnapTrim异步完成并通过一系列osd_snap_trim_*参数控制优先级、成本与节流在保证数据一致性的同时将对正常 IO 的影响降到最低。关键源码索引关注点位置PGPool 结构体定义与构造函数src/osd/PeeringState.hPGPool::update 增量更新逻辑src/osd/PeeringState.ccload_pgs 从文件存储恢复 mapsrc/osd/OSD.cc_make_pg 构造 PGPool 与 PrimaryLogPGsrc/osd/OSD.ccpg_pool_t::snap_epoch / removed_snapssrc/osd/osd_types.hpg_info_t::purged_snapssrc/osd/osd_types.hOSDMap 增量快照字段与访问器src/osd/OSDMap.h、src/osd/OSDMap.hon_activate 初始化 snap_trimqsrc/osd/PG.ccon_active_advmap 合并增量快照状态src/osd/PG.ccqueue_for_snap_trim 入调度器src/osd/OSD.cc快照裁剪节流与统计src/osd/OSD.cc、src/osd/OSD.cc阅读建议先通读本文第三节的PGPool::update与第五节的on_active_advmap这两处是理解快照删除状态如何在 OSD 内部流转的关键入口再结合第六节的调度参数理解异步裁剪的工程权衡。【免费下载链接】cephCeph is a distributed object, block, and file storage platform项目地址: https://gitcode.com/gh_mirrors/ce/ceph创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。