分布式系统中 Map 增量(Delta)是否需要持久化

张开发
2026/5/1 6:50:35 15 分钟阅读

分享文章

分布式系统中 Map 增量(Delta)是否需要持久化
以 Ceph OSDMap 为例结合自研分布式文件系统的设计决策一、背景什么是 Map 增量在分布式系统中通常有一个集群拓扑视图Map比如Ceph 的 OSDMap描述每个 OSD 的状态、CRUSH 拓扑自研系统的 PartitionMap描述每个 partition 的归属 MDS 和状态这个 Map 会随集群状态变化频繁更新每次更新产生一个新 epoch。全量推送每次变更都推送完整的 Map简单但数据量大增量推送Delta只推送变化的部分节省带宽但实现复杂核心问题来了这些增量Delta需要持久化到 DB 吗二、Ceph OSDMap 为什么必须持久化增量2.1 OSD 需要历史 epoch 定位 PGCeph 使用 CRUSH 算法根据 OSDMap 计算 PGPlacement Group的位置。同一个 PG 在不同 epoch 的 OSDMap 下计算出的 OSD 位置可能不同。PG 1.0a 在 epoch100 时 → 存在 [osd.1, osd.5, osd.9] PG 1.0a 在 epoch95 时 → 存在 [osd.1, osd.3, osd.9] osd.3 还活着当 OSD 之间做数据迁移backfill/recovery时需要知道“这个 PG 在旧 epoch 时应该在哪些 OSD 上”“数据从哪里迁移到哪里”如果 Monitor 只保留最新全量 OSDMapOSD 拿着一个老 epoch 的视图来询问 MonitorMonitor 无法重建那个历史时刻的拓扑就无法指导正确的数据迁移。2.2 具体场景落后很多的 OSD 追赶当前 epoch 500 某 OSD 宕机了一段时间重启后持有的 epoch 450 OSD 向 Monitor 请求追赶build_incremental(450 → 500) Monitor 需要 epoch 451 的 Incremental包含哪个 OSD 出入/权重变化 epoch 452 的 Incremental ... epoch 500 的 Incremental 每个 Incremental 都记录了从 N-1 到 N 发生了什么变化 OSD 逐步 apply最终追赶到最新 epoch 如果 Monitor 没有保存历史 Incremental 只能发 epoch500 的全量 OSD 知道了现在是什么样但不知道中间发生了什么 → 无法正确计算哪些 PG 需要迁移数据因为不知道迁移路径2.3 OSDMap Incremental 的持久化内容每个 epoch 的 Incremental 存储在 Monitor 的 RocksDB 中key: osdmap/v{epoch} → 存该 epoch 的 Incremental变化集 key: osdmap/full/{epoch} → 每隔若干 epoch 存一个全量快照Incremental 记录structOSDMap::Incremental{epoch_t epoch;mapint32_t,entity_addr_tnew_up_client;// 新上线的 OSDsetint32_tnew_down;// 新下线的 OSDmapint32_t,uint32_tnew_weight;// 权重变化mapint32_t,pg_tnew_pg_temp;// PG 临时映射变化// ...};2.4 Trim 策略历史不会无限保留Monitor 会定期清理太老的历史 epochversion_tOSDMonitor::get_trim_to()const{// 至少保留 mon_min_osdmap_epochs 个 epoch默认 500// 至少保留到所有 OSD 都 clean 到的最老 epochmin_last_epoch_clean}超出保留范围的历史 epoch 被 GC 删除。客户端/OSD 落后太多时直接发全量快照 从该时间点之后的增量。三、什么情况下不需要持久化增量与 OSDMap 形成对比很多系统的 Map 不需要持久化增量条件一接收方不依赖历史路径只需要当前状态如果接收方MDS、客户端只需要知道partition 现在在哪里而不需要知道中间经历了哪些迁移路径那么全量快照持久化DB 里始终有最新的完整 Map增量只在内存中缓存少量历史用于追赶近期落后的接收方落后太多时降级为推全量内存缓存的增量是性能优化不是正确性保证。条件二接收方重连时能接受全量重建如果接收方MDS重启后从 mgmtd 拉一次全量 PartitionMap 就能正常工作不需要回放历史变更序列则增量不需要持久化。条件三Map 数据量小全量推送开销可接受PartitionMap 即使有 1000 个 partition全量序列化也就几十 KB全量推送的代价很低。四、决策框架如何判断是否需要持久化增量问题一接收方是否需要历史路径来保证正确性 是 → 必须持久化增量如 Ceph OSDMap 否 → 继续下一个问题 问题二接收方落后时全量推送是否可接受带宽/延迟 可接受 → 不需要持久化增量内存缓存少量即可 不可接受Map 极大→ 考虑持久化增量或分片推送 问题三mgmtd 重启后是否需要快速恢复增量推送能力 否重启后推全量给所有 MDS 即可→ 不需要持久化 是重启后要能继续发增量不想推全量→ 需要持久化增量五、两类系统的对比系统Map 类型增量是否持久化原因Ceph OSDMap拓扑路由 Map✅ 必须持久化OSD recovery 依赖历史路径计算数据迁移Ceph FSMapMDS 状态 Map❌ 不持久化增量MDS 只需要最新状态全量很小Ceph MDSMapMDS 状态 Map❌ 不持久化增量同上自研 PartitionMappartition 路由 Map❌ 不需要持久化MDS/客户端只需当前状态全量可接受Kubernetes etcd所有资源状态✅ 持久化WAL强一致性事件溯源需要完整历史TiKV PD Region MapRegion 路由 Map❌ 只存全量TiKV 只需要当前 Region 分布六、自研 PartitionMap 的推荐实现持久化DB PartitionMap 全量快照每次变更后持久化最新全量 key: partition_map/v{epoch} → 全量 key: partition_map/latest → 最新 epoch 指针 内存不持久化 delta_cache: RingBufferPartitionMapDelta, 20 // 保留最近 20 个 epoch 的增量用于追赶近期落后的 MDS // mgmtd 重启后 delta_cache 清空落后的 MDS 推全量 推送策略 MDS 心跳携带 current_epoch if mds_epoch delta_cache.oldest_epoch: 推送增量Delta追赶到最新 else: 推送全量Full七、总结OSDMap 持久化增量是因为 OSD recovery 在语义上依赖历史 epoch 的拓扑路径这是正确性需求不是性能优化。PartitionMap 不需要持久化增量因为 MDS 和客户端只需要当前状态接收方重建时拉全量即可。增量只是减少推送量的性能优化放内存缓存就够了。判断标准一句话接收方是否需要历史路径来保证行为正确需要 → 持久化不需要 → 内存缓存即可。

更多文章