注意
本文档适用于 Ceph 的开发版本。
OSDMap 修剪和 PastIntervals
PastIntervals(过去的时间间隔)
在两种情况下,我们需要考虑某个 PG 回溯到某个 epoch e 的所有 acting-set OSD 的集合
在对等(peering)过程中,我们需要考虑从当前 OSDMap 回溯到
last_epoch_started的每个 epoch 的 acting set,last_epoch_started是 PG 完成对等并变为活动的最后一个 epoch。(有关详细解释,请参阅 last_epoch_started)在恢复过程中,我们需要考虑从当前 OSDMap 回溯到
last_epoch_clean的每个 epoch 的 acting set,last_epoch_clean是 acting set 中的所有 OSD 完全恢复且 acting set 完整的最后一个 epoch。
对于上述任一目的,我们可以通过从当前 OSDMap 向后迭代到相关 epoch 来构建这样一个集合。相反,我们为每个 PG 维护一个名为 PastIntervals 的结构。
一个 interval(时间间隔)是 OSDMap epoch 的连续序列,其中 PG 映射没有改变。这包括对 acting set、up set、primary 以及 PastIntervals::check_new_interval 中详细列出的其他几个参数的更改。
维护和修剪
PastIntervals 结构存储了回溯到 last_epoch_clean 的每个 interval 的记录。在每个新的 interval(参见 AdvMap 反应、PeeringState::should_restart_peering 和 PeeringState::start_peering_interval)中,具有该 PG 的每个 OSD 将新的 interval 添加到其本地 PastIntervals。发送给尚不具有该 PG 的 OSD 的激活消息包含发送方的 PastIntervals,以便接收方无需重新构建它。(参见 PeeringState::activate needs_past_intervals)。
PastIntervals 在两个地方被修剪。首先,当 primary 将 PG 标记为 clean 时,它会清除其 past_intervals 实例 (PeeringState::try_mark_clean())。当 replicas 收到信息时,它们也会做同样的事情(参见 PeeringState::update_history)。
第二种情况更复杂,发生在 PeeringState::start_peering_interval 中。如果出现“map gap”(映射间隙),我们假设 PG 实际上已经变为 clean,但我们尚未收到带有更新的 last_epoch_clean 值的 pg_info_t。为了解释这种行为,我们需要讨论 OSDMap 修剪。
OSDMap 修剪
OSDMap 由 Monitor 仲裁创建并传播给 OSD。Monitor 集群还决定何时允许 OSD(和 Monitors)修剪旧的 OSDMap epoch。出于本文档前面解释的原因,主要约束是必须保留所有 OSDMap,回溯到某个 epoch,使得所有 PG 在该 epoch 或更晚的 epoch 都已 clean (min_last_epoch_clean)。(参见 OSDMonitor::get_trim_to)。
Monitor 仲裁通过每个 OSD 定期发送的 MOSDBeacon 消息确定 min_last_epoch_clean。每条消息包含 OSD 在那一刻作为 primary 的 PG 集合以及该集合的 min_last_epoch_clean。Monitors 在 OSDMonitor::last_epoch_clean 中跟踪这些值。
OSD 用于填充 MOSDBeacon 的 min_last_epoch_clean 值存在一个细微差别。OSD::collect_pg_stats 调用 PG::with_pg_stats 来获取 lec 值,它实际上使用 pg_stat_t::get_effective_last_epoch_clean() 而不是 info.history.last_epoch_clean。如果 PG 当前是 clean 的,pg_stat_t::get_effective_last_epoch_clean() 是当前 epoch 而不是 last_epoch_clean——这之所以有效,是因为 PG 在该 epoch 是 clean 的,并且它允许在创建 OSDMap 的期间(可能由于快照活动)修剪 OSDMap,但没有 PG 正在经历 interval 更改。
回到 PastIntervals
我们现在可以理解上面的第二种修剪情况。如果 OSDMap 已修剪到 epoch e,我们知道 PG 必须在某个 epoch >= e 时是 clean 的(事实上,**所有** PG 都必须是 clean 的),因此我们可以丢弃我们的 PastIntevals。
这种依赖关系也出现在 PeeringState::check_past_interval_bounds() 中。PeeringState::get_required_past_interval_bounds 将最旧的 epoch 作为参数,它来自 OSDSuperblock::cluster_osdmap_trim_lower_bound。我们使用 cluster_osdmap_trim_lower_bound 而不是特定 osd 的最旧 map,因为我们不一定修剪所有 MOSDMap::cluster_osdmap_trim_lower_bound。为了避免一次做太多工作,我们使用 osd_target_transaction_size 在 OSD::trim_maps() 中限制修剪的 osdmaps 数量。因此,特定 OSD 的最旧 map 可能会落后于 OSDSuperblock::cluster_osdmap_trim_lower_bound 一段时间。
请参阅 https://tracker.ceph.com/issues/49689 以获取示例。
OSDSuperblock::maps
OSDSuperblock 持有一个 epoch interval set,代表 OSD 存储的 OSDMap。处理的每个 OSDMap epoch 范围都会添加到该集合中。一旦 osdmap 被修剪,它将从集合中删除。因此,集合的下限代表存储的最旧 map,而上限代表最新的 map。
interval_set 数据结构支持非连续的 epoch interval,这可能发生在“map gap”事件中。在使用这个数据结构之前,oldest_map 和 newest_map epoch 存储在 OSDSuperblock 中。然而,保持单个连续的 epoch 范围会施加约束,可能导致 OSDMap 泄漏。