注意

本文档适用于 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_mapnewest_map epoch 存储在 OSDSuperblock 中。然而,保持单个连续的 epoch 范围会施加约束,可能导致 OSDMap 泄漏。

请参阅: https://tracker.ceph.com/issues/61962

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入