打破规模壁垒:我们如何在 Intercom 优化 Elasticsearch 的使用

已发表: 2022-09-22

Elasticsearch 是 Intercom 不可或缺的一部分。

它支持核心对讲功能,如收件箱、收件箱视图、API、文章、用户列表、报告、Resolution Bot 和我们的内部日志系统。 我们的 Elasticsearch 集群包含超过 350TB 的客户数据,存储超过 3000 亿个文档,峰值每秒处理超过 6 万个请求。

随着 Intercom 的 Elasticsearch 使用量增加,我们需要确保我们的系统扩展以支持我们的持续增长。 随着我们最近推出的下一代收件箱,Elasticsearch 的可靠性比以往任何时候都更加重要。

我们决定解决我们的 Elasticsearch 设置存在的一个问题,该问题会带来可用性风险并威胁到未来的停机时间:我们 Elasticsearch 集群中节点之间的流量/工作分布不均。

低效率的早期迹象:负载不平衡

Elasticsearch 允许您通过增加存储数据的节点(数据节点)的数量来进行水平扩展。 我们开始注意到这些数据节点之间的负载不平衡:由于磁盘或 CPU 使用率较高,其中一些节点比其他节点承受更大的压力(或“更热”)。

图 1 CPU 使用不平衡
(图 1)CPU 使用不平衡:两个热节点的 CPU 使用率比平均值高约 20%。

Elasticsearch 的内置分片放置逻辑基于粗略估计每个节点中的可用磁盘空间和每个节点索引的分片数量的计算来做出决策。 分片的资源利用率不计入此计算。 结果,一些节点可能会收到更多资源匮乏的分片并变得“热”。 每个搜索请求都由多个数据节点处理。 在高峰流量期间超过其限制的热节点可能会导致整个集群的性能下降。

热节点的一个常见原因是分片放置逻辑将大分片(基于磁盘利用率)分配给集群,从而降低了平衡分配的可能性。 通常,一个节点可能会比其他节点分配更多的大分片,从而使其磁盘利用率更高。 大分片的存在也阻碍了我们增量扩展集群的能力,因为添加数据节点并不能保证所有热节点的负载减少(图 2)。

图 4 添加节点

(图2)增加一个数据节点并没有减少Host A的负载。增加一个节点可以减少Host A的负载,但是集群仍然会出现负载分布不均的情况。

相比之下,随着集群的扩展,拥有更小的分片有助于减少所有数据节点上的负载——包括“热”节点(图 3)。

图 3 许多较小的分片

(图 3)拥有许多较小的分片有助于减少所有数据节点上的负载。

注意:问题不仅限于具有大型分片的集群。 如果我们将“大小”替换为“CPU 利用率”或“搜索流量”,我们会观察到类似的行为,但比较大小更容易可视化。

除了影响集群稳定性外,负载不平衡还会影响我们经济高效地扩展的能力。 我们总是需要增加比需要更多的容量,以将较热的节点保持在危险水平以下。 解决这个问题意味着通过更有效地利用我们的基础设施来获得更好的可用性和显着的成本节约。

我们对问题的深刻理解帮助我们意识到,如果我们有以下条件,负载可以分布得更均匀:

  • 相对于数据节点数量的更多分片。 这将确保大多数节点收到相同数量的分片。
  • 相对于数据节点大小的更小的分片。 如果为某些节点提供了一些额外的分片,则不会导致这些节点的负载有任何有意义的增加。

纸杯蛋糕解决方案:更少的大节点

分片数量与数据节点数量的比率,以及分片大小与数据节点大小的比率,可以通过拥有更多的小分片来调整。 但是可以通过移动到更少但更大的数据节点来更轻松地对其进行调整。

我们决定从一个纸杯蛋糕开始来验证这个假设。 我们将一些集群迁移到更大、更强大且节点更少的实例——保持相同的总容量。 例如,我们将一个集群从 40 个 4xlarge 实例移动到 10 个 16xlarge 实例,通过更均匀地分布分片来减少负载不平衡。

图 4 磁盘和 CPU 之间更好的负载分布

(图 4)通过移动到更少的更大节点,更好地分配磁盘和 CPU 之间的负载。

较少的较大节点缓解验证了我们的假设,即调整数据节点的数量和大小可以改善负载分布。 我们本可以停在那里,但这种方法有一些缺点:

  • 我们知道,随着分片随着时间的推移变大,或者如果将更多节点添加到集群中以解决流量增加,负载不平衡会再次出现。
  • 更大的节点使增量扩展更加昂贵。 即使我们只需要一点额外的容量,现在添加一个节点也会花费更多。

挑战:跨越压缩普通对象指针 (OOP) 阈值

迁移到更少的更大节点并不像更改实例大小那么简单。 我们面临的一个瓶颈是在迁移时保留可用的总堆大小(一个节点上的堆大小 x 节点总数)。

正如 Elastic 建议的那样,我们一直将数据节点中的堆大小限制在 ~30.5 GB,以确保我们保持在临界值以下,以便 JVM 可以使用压缩的 OOP。 如果我们在迁移到更少、更大的节点后将堆大小限制为 ~30.5 GB,我们将减少整体堆容量,因为我们将使用更少的节点。

“我们迁移到的实例非常庞大,我们希望将大部分 RAM 分配给堆,以便我们有空间用于指针,并有足够的空间用于文件系统缓存”

我们找不到很多关于跨越这个门槛的影响的建议。 我们要迁移到的实例非常庞大,我们希望将大部分 RAM 分配给堆,以便我们有空间用于指针,并有足够的空间用于文件系统缓存。 我们通过将生产流量复制到测试集群来尝试一些阈值,并将 RAM 的 33% 到 42% 作为 RAM 超过 200 GB 的机器的堆大小。

堆大小的变化对各种集群的影响不同。 虽然一些集群在“JVM % heap in use”或“Young GC Collection Time”等指标上没有变化,但总体趋势是增加。 无论如何,总的来说这是一次积极的体验,我们的集群在这种配置下已经运行了 9 个多月——没有任何问题。

长期修复:许多较小的碎片

一个长期的解决方案是,相对于数据节点的数量和大小,转向拥有更多数量的小分片。 我们可以通过两种方式获得更小的分片:

  • 迁移索引以获得更多主分片:这会将索引中的数据分布在更多分片中。
  • 将索引分解为更小的索引(分区):这会将索引中的数据分布在更多索引中。

重要的是要注意我们不想创建一百万个小分片,或者有数百个分区。 每个索引和分片都需要一些内存和 CPU 资源。

“我们专注于更容易地在我们的系统中试验和修复次优配置,而不是专注于‘完美’配置”

在大多数情况下,一小组大分片比许多小分片使用更少的资源。 但是还有其他选择——实验应该可以帮助您获得更适合您的用例的配置。

为了使我们的系统更具弹性,我们专注于更容易地在我们的系统中进行实验和修复次优配置,而不是固定在“完美”配置上。

分区索引

增加主分片的数量有时会影响聚合数据的查询的性能,我们在迁移负责 Intercom 报告产品的集群时遇到了这种情况。 相反,将一个索引划分为多个索引可以将负载分配到更多分片上,而不会降低查询性能。

对讲不需要为多个客户共同定位数据,因此我们选择根据客户的唯一 ID 进行分区。 这通过简化分区逻辑和减少所需设置帮助我们更快地交付价值。

“为了以对我们工程师现有习惯和方法影响最小的方式对数据进行分区,我们首先投入了大量时间来了解我们的工程师如何使用 Elasticsearch”

为了以对工程师现有习惯和方法影响最小的方式对数据进行分区,我们首先投入了大量时间来了解我们的工程师如何使用 Elasticsearch。 我们将可观察性系统深度集成到 Elasticsearch 客户端库中,并扫描我们的代码库以了解我们团队与 Elasticsearch API 交互的所有不同方式。

我们的故障恢复模式是重试请求,因此我们在发出非幂等请求的地方进行了必要的更改。 我们最终添加了一些 linter 来阻止使用诸如 `update/delete_by_query` 之类的 API,因为它们使发出非幂等请求变得容易。

我们构建了两种功能,它们协同工作以提供完整的功能:

  • 一种将请求从一个索引路由到另一个索引的方法。 这个其他索引可以是一个分区,或者只是一个非分区索引。
  • 一种将数据双重写入多个索引的方法。 这使我们能够使分区与正在迁移的索引保持同步。

“我们优化了我们的流程,在不影响速度的情况下最大限度地减少任何事故的爆炸半径”

总而言之,将索引迁移到分区的过程如下所示:

  1. 我们创建新分区并打开双写,以便我们的分区与原始索引保持同步。
  2. 我们触发所有数据的回填。 这些回填请求将被双重写入新分区。
  3. 回填完成后,我们验证旧索引和新索引是否具有相同的数据。 如果一切正常,我们使用功能标志开始为少数客户使用分区并监控结果。
  4. 一旦我们有信心,我们会将所有客户移动到分区,同时对旧索引和分区进行双重写入。
  5. 当我们确定迁移成功后,停止双写并删除旧索引。

这些看似简单的步骤包含了很多复杂性。 我们优化了我们的流程,以最大限度地减少任何事件的爆炸半径,同时不影响速度。

收获利益

这项工作帮助我们改善了 Elasticsearch 集群中的负载平衡。 更重要的是,我们现在可以通过将索引迁移到主分片较少的分区来改善负载分布,从而实现两全​​其美:每个索引的分片越来越少。

应用这些知识,我们能够释放重要的性能提升和节省。

  • 我们将两个集群的成本分别降低了 40% 和 25%,并且在其他集群上也显着节省了成本。
  • 我们将某个集群的平均 CPU 利用率降低了 25%,并将请求延迟中值提高了 100%。 我们通过将高流量索引迁移到与原始分区相比每个分区具有更少主分片的分区来实现这一点。
  • 迁移索引的通用能力还使我们能够更改索引的架构,从而允许产品工程师为我们的客户构建更好的体验,或者使用更新的 Lucene 版本重新索引数据,从而解锁我们升级到 Elasticsearch 8 的能力。

图 5 负载不平衡和 CPU 利用率的改善

(图 5)通过将高流量索引迁移到每个分区的主分片较少的分区,负载不平衡改善了 50%,CPU 利用率提高了 25%。

图 6 中值请求延迟

(图 6)通过将高流量索引迁移到每个分区的主分片较少的分区,平均请求延迟平均提高了 100%。

下一步是什么?

引入 Elasticsearch 来支持新产品和功能应该很简单。 我们的愿景是让我们的工程师与 Elasticsearch 交互变得像现代 Web 框架使其与关系数据库交互一样简单。 团队应该很容易创建索引、从索引读取或写入、更改其架构等等——而不必担心如何处理请求。

您对我们的工程团队在 Intercom 的工作方式感兴趣吗? 在此处了解更多信息并查看我们的空缺职位。

职业 CTA - 工程(水平)