Luminous 中的新增功能:PG 过量保护

2017年10月25日 sage

为你的集群选择合适的 PG(“放置组”)数量有点像一门黑艺术——而且是一种可用性噩梦。获得一个合理的值会对集群的性能和可靠性产生重大影响,好的方面和坏的方面都有。不幸的是,在过去的几年里,我们经历过很多坏的情况。在 Luminous 中,我们采取了重大步骤,最终消除了导致你的集群陷入困境的最常见方式之一,并且展望未来,我们旨在最终隐藏 PG,以便大多数用户永远不必了解或思考它们。

背景

RADOS 中的对象存储在逻辑池中。每个池被划分为 pg_num 个放置组 (PG),以便每个 PG 包含池中对象的某个片段。如果它是复制的 PG,你将在 CRUSH 决定 PG 应映射到的每个 OSD 上获得这些对象的相同副本。如果它是纠删码池,每个 PG 实例包含 PG 中每个对象的不同分片,以便可以从 k+m 个 PG 实例中的 k 个重建对象的内容。因此,如果你有一个 3 倍复制的池,有 128 个 PG,你将在你拥有的 OSD 上分布 384 个 PG 实例。如果它是一个 8+3 纠删码池,则为 1408 个 PG 实例。

PG 有两个主要用途

  1. 数据分布。特别是,如果单个 OSD 发生故障,其存储的其他 PG 的副本分布在许多其他 OSD 上,以便当集群自我修复时,工作可以并行化到集群中的许多来源和目标,从而减少恢复时间和影响。
  2. OSD 内的并行性。在每个 OSD 内部,我们使用 PG 对工作进行分片,以允许跨核心的并行性并减少锁争用。

经验法则是在每个 OSD 上需要大约 100 个 PG 实例。如果你少于这个数量,负载分布不会那么平衡(某些 OSD 将存储比其他 OSD 更多的数据),如果你多于这个数量,那么每个 OSD 将与太多的其他 OSD 进行协调,从而增加开销并降低整体可靠性。

更改 pg_num

选择合适的 PG 数量非常重要的原因是池的 pg_num 值可以增加——将现有的 PG 拆分并迁移到更小的片段中——但不能减少。拆分相对容易实现(并且自 Bobtail 以来一直是可行的),因此如果你低估了 PG 数量或你的集群随着时间的推移而增长,你可以修复它。但是,如果你有太多的 PG,你目前还不能将它们合并在一起。(我们正在研究添加此功能,但还需要一两个版本。)此外,即使你可以拆分 PG,这样做也是一项相对昂贵的操作。例如,将 PG 数量翻倍会将集群中的一半数据移动。

因此,许多操作员选择 overshoot 推荐的 PG 数量,期望集群会增长。在增长实现之前,集群将表现不佳。更常见的是,操作员不知道应该选择哪个值,最终选择了一个不太理想的值(或者使用了一个做出糟糕选择的自动化部署工具)。

过去间隔的问题

仅仅拥有大量的 PG 通常本身不是问题。但是,如果集群变得不健康,特别是如果它在一段时间内保持不健康,那么各种因素的组合可能会导致问题。

为了促进 OSD 故障和/或 PG 迁移到其他 OSD 后的正确恢复,每个 PG 都会跟踪自 PG 上次“干净”或完全健康以来的所谓 过去间隔。在这种情况下,一个间隔是系统决定 PG 应该存储在特定 OSD 集合上的时间段。当系统“挣扎”时——例如,当 OSD 由于网络不稳定或硬件不可靠或某些错误配置而停止和重新启动并再次停止时——可以在短时间内生成许多过去间隔,并且我们必须记住所有这些。如果这些更改发生得很快,并且数据实际上还没有迁移到新位置,那么过去间隔的列表可能会变得非常大。如果集群在一段时间内不健康(例如,几天甚至几周),那么过去间隔集合可能会变得足够大,需要大量的内存。并且,如果系统配置了大量的 PG(可能比它应该有的更多),问题会变得更糟,因为为每个 PG 维护过去间隔集合。

在极端情况下,OSD 守护程序最终可能会消耗过多的内存,以至于被内核杀死。然后它们会自动由 init 系统(或管理员)重新启动,集群状态更改会生成另一个要添加到过去间隔集合中的间隔,使问题变得更糟。

“min in”限制的问题

与传统的 RAID 系统不同,Ceph 秉持“待机”设备是浪费设备的理念:为什么不立即使用驱动器,然后在发生故障时,将剩余的工作分散到幸存的设备上?这通常在少量设备发生故障时效果很好。每次 OSD 发生故障时,数据都会被压缩到剩余的 OSD 上一点点。

然而,最终我们必须决定这不能无限期地进行下去。mon_osd_min_in_ratio 配置参数控制系统中必须“在线”(即在线并实际存储数据)的所有 OSD 的最小比例,以便将另一个下线的 OSD 标记为“离线”并将数据重新复制到剩余的 OSD 上。直到最近,此值默认为 .3,或 30%,这意味着如果你从一个具有每个 OSD 100 个 PG 的集群开始,并稳步地使每个 OSD 发生故障,你的数据将继续缩小到更小的集合,直到剩余的 30% 的 OSD 最终拥有超过 300 个 PG。

上述两个问题的结合导致了几个集群陷入明显的螺旋式死亡,其中 OSD 无法启动或恢复,而无需管理员和开发人员采取各种英勇措施来减少其内存需求并使其恢复健康。

在 Luminous 中修复所有问题

在看到这种情况几次并拿出相同的技巧来使集群恢复在线后,很明显我们可以做几件事来防止这种情况在未来发生。首先,容易的事情

  • 我们将 mon_osd_min_in_ratio 增加到更 合理的默认值 0.75,这意味着一旦 25% 的设备发生故障,集群将停止尝试自行修复,并需要操作员干预。当然,你可以根据你的环境调整此值。
  • 我们对集群中你可以使用的 PG 数量设置了一些 更好的限制。现在有一个 mon_max_pg_per_osd 限制(默认值:200),如果创建新池或调整现有池的 pg_num 或副本计数会超过配置的限制(通过将总 PG 实例数除以“在线”OSD 的总数确定),则会阻止你这样做。同样,如果你真的需要,你可以增加限制,但 2 倍的推荐值似乎是一个合理的点,可以防止用户给自己带来麻烦。

然而,真正的问题是过去间隔的高内存使用以及集群——通过任何序列的故障或错误配置——可能会尝试将过多的 PG 实例化到单个 OSD 上。

过去间隔内存

在 Luminous 版本发布之前进行的主要后台重构之一是对过去间隔跟踪进行重写。以前,我们会保留自 PG 上次干净以来所有间隔的列表,标记哪些间隔可能包含写入,并将该列表保留下来(免费共享),直到 PG 最终完全“干净”为止。然而,事实证明,了解 PG 位置的完整历史记录比我们真正需要的更多信息。PG 对等算法 实际上 需要知道的是它需要在对等过程中联系哪些 OSD,以便确定它是否发现了任何过去的更新,这可以从(粗略地说)“最小 OSD 集合”中推断出来,这些 OSD 可能已经处理了更新。当过去间隔跟踪结构围绕所需的确切信息重建时,它变得非常紧凑,并且相对不敏感于长时间的集群不健康期,从而消除了导致许多问题的内存增长源。万岁!

每个 OSD 上 PG 的硬限制

另一个核心问题是,任何单个 OSD 都不能实例化它能够处理的超过的 PG 数量。由于 PG 映射是 CRUSH 中数学计算的结果,任何 CRUSH 或集群状态更改都可能导致 CRUSH 将过多的 PG 放在一个地方的情况。即使我们解决了上述“min in”问题,其他场景或错误配置也可能导致单个 OSD 上有太多的 PG。

在 Luminous 中,我们添加了对单个 OSD 可以实例化的 PG 数量的硬限制,表示为 osd_max_pg_per_osd_hard_ratio,它是 mon_max_pg_per_osd 限制的倍数(我们上面提到的防止你配置过多 PG 的限制)。如果任何单个 OSD 被要求创建超过它应该的数量的 PG,它将简单地拒绝并忽略该请求。如果发生这种情况,集群将部分不可用(这些 PG 无法对等并服务 IO,直到数据分布得到纠正),但我们将避免将系统置于难以恢复的境地。希望管理员会在发生这种情况之前被提醒并参与进来,但在极端疏忽的情况下,系统不会让自己陷入困境。

结论

听到过去经常出错但现在无法再出错的事情并不像阅读闪亮的新功能那么有趣,但我们希望这能让经历过这些问题(或险些经历极端情况)的用户安心。我们当然睡得更香了!