作者:来自 Elastic  Ben Chaplin

一次技术解析:在 Elasticsearch Serverless 中,两套副本系统(一个用于故障切换,一个用于负载均衡)如何每五分钟合并为每个索引的单一缓存感知推荐方案。

摆脱运维负担,使用 Elastic Cloud Serverless。它可以自动扩展、处理流量峰值,让你专注于构建应用——现在可以开始 14 天免费试用亲自体验!

你也可以参考这些指南来构建 AI 驱动的搜索体验,或跨业务系统和软件进行搜索


最近的一篇文章中,我们介绍了 Elastic Cloud Serverless 中的一个新系统,它可以根据搜索负载自动调整索引的副本数量。在本文中,我们将整体回顾用于确定最优副本数量的完整流程。同时,我们还会深入探讨增加副本时的一个关键因素 —— 缓存容量,并解释每种副本系统如何在当前分配的磁盘空间条件下,确保数据完全可缓存。

Serverless 基础知识

Elasticsearch Serverless 中,对象存储是唯一的事实来源(single source of truth)。索引(indexing)节点 持有所有主分片(primary shards),负责写入数据并将其上传到对象存储;搜索节点(search nodes) 持有副本分片(replica shards),负责执行所有搜索,并从对象存储读取数据。因此,在 Serverless 中,每个索引至少总是有一个副本。搜索节点越多,就越有可能容纳更多副本。

这两个层级(tier)会根据 Serverless 项目的需求独立扩展。搜索层的扩展基于 搜索能力(search power),这是一个用户配置项,用于决定集群可用资源范围,包括用于缓存数据的最小磁盘空间。具体来说,这里缓存的是被增强的数据(boosted data):即用户定义的 提升窗口(boost window)内最近的、基于时间的(@timestamp)文档,以及所有非时间型文档。

用于即时故障切换的副本(RfIF):根据 search power 决定 1 或 2 个副本

早在 2024 年,我们就发布了第一个副本系统 Replicas for instant failover(RfIF),它用于决定在 Serverless 中是否为某个索引分配一个或两个副本。在 Serverless 中,对象存储提供持久性,但真正让查询变快的是在搜索节点上直接缓存的数据。两个副本可以在搜索节点意外故障时提供更好的容错能力。然而,只有当这些额外的数据副本能够被缓存空间容纳时,增加副本才是有效的。

RfIF 会根据 search power 所保证的缓存空间来做决策。在较低成本的配置下,只能在磁盘上容纳一份 boosted data 的副本。在这种情况下,所有索引都只会有一个副本。随着 search power 提升,我们可以利用更多缓存空间,为部分索引提供两个副本。最终,在最高的 search power 设置下,有足够的缓存空间让所有索引都拥有两个副本。

用于负载均衡的副本(RfLB):在搜索负载下动态扩展副本

用于负载均衡的副本系统(Replicas for load balancing,RfLB) 于 2026 年初发布。之前的一篇博客文章 对该系统做了详细解释(如果你喜欢 “披萨”,那篇文章很值得一读)。简单来说,RfLB 的目标是在高搜索负载时为索引增加副本。更多副本意味着更高的吞吐量(throughput),因为可以有更多搜索节点参与返回结果。

两个系统,一个推荐结果

RfIF 和 RfLB 都会每 5 分钟运行一次,并为每个索引生成一个推荐副本数量。RfIF 基于 search power 和 boosted data 的规模,推荐 1 或 2 个副本。RfLB 则基于搜索负载推荐 1 到 N 个副本,其中 N 是当前搜索节点数量。

对于每个索引,我们会取两个系统推荐值中的较大者。随后,这些推荐结果会进入一个缓存预算系统(cache budgeting system),该系统可能会根据可用缓存空间对副本数量进行限制。

Map<String, Integer> rfifRecs = RfIF.run(allIndices)
Map<String, Integer> rflbRecs = RfLB.run(allIndices)

Map<String, Integer> jointRecs = new Map()
for (index in allIndices) {
  rec = max(rfifRecs.get(index), rflbRecs.get(index))
  jointRecs.put(index, rec)
}

Map<String, Integer> finalRecs = cacheBudgetingSystem(jointRecs)

回顾上一篇文章,如果最终的推荐结果是增加副本数量,那么我们会立即应用该变更;但如果推荐结果是减少副本数量,我们只会记录这个信号。只有在大约 30 分钟内持续收到 “需要减少副本” 的信号后,才会真正应用新的副本数量。

缓存预算:确保副本可以放入磁盘

接下来我们更深入看最后一步:缓存预算系统(cache budgeting system)。如果我们配置了过多副本,一个足够广泛的查询集合可能会导致频繁的缓存驱逐(cache evictions),从而使查询变慢。为了避免这种情况,我们有时会降低副本系统的推荐值,以确保所有副本都能放入缓存中。RfIF 已经会根据 search power 来做这种控制,但 RfLB 可能会在某些高搜索负载的索引上推荐更高的副本数量。而缓存预算系统正是用来限制这些情况 —— 也就是当 RfLB 的推荐值超过 RfIF 时进行约束。

下面是该算法的伪代码(以及后续解释):

long remainingBytes = totalCacheBudget 
  - cacheUsedByCurrentReplicas
  + cacheFreedByDecreases
  - cacheNeededForRfIFIncreases

Map<String, Integer> finalRecs = new Map()
for (index in allIndicesRankedByLoad) {
  long additionalReps = rflbRecs.get(index) - rfifRecs.get(index)
  if (additionalReps <= 0) {
    finalRecs.put(index, rfifRecs.get(index))
  } else {
    int replicasThatFit = floor(remainingBytes / index.boostedData)
    int granted = min(additionalReps, replicasThatFit)
    finalRecs.put(index, rfifRecs.get(index) + granted)
    remainingBytes -= granted * index.boostedData
  }
}

这段代码的第一行包含很多信息:

  1. totalCacheBudget 是集群中为缓存数据分配的预置磁盘总量。
  2. cacheUsedByCurrentReplicas 是当前副本配置已经使用的缓存量。
  3. cacheFreedByDecreases 表示由于副本减少而释放的缓存空间,这些空间在新推荐结果下会被释放出来,是可以再次利用的重要缓存资源。
  4. cacheNeededForRfIFIncreases 反映一个事实:在这个缓存预算系统中,我们不会限制 RfIF 的推荐。因此必须把 RfIF 推荐带来的所有新增缓存消耗也计算进去。

在综合这些因素后,remainingBytes 精确表示:在扣除 RfIF 所需增长之后,我们还能用于 RfLB 推荐额外副本的缓存空间。

接下来的算法会遍历所有索引(allIndicesRankedByLoad 是按搜索负载排序的索引列表,优先处理负载更高的索引),并利用这些 remainingBytes 来逐步分配副本。该过程确保我们在现有字节预算内尽可能多地“挤出”副本。

例如,如果 RfLB 建议增加 4 个副本,但实际只剩下容纳 2 个副本的空间,那么最终只会增加 2 个。

总结

Serverless 中的副本由两个协同系统共同决定:一个专注于即时故障切换的冗余机制,另一个专注于在高搜索负载下提升吞吐量。这两个系统并行运行,为每个索引共同生成副本决策,并检查这些决策是否能被缓存容量所承载。最终的结果是:Serverless 能够在负载变化时自适应扩展,同时始终遵守底层资源边界,保证数据可缓存并维持高性能搜索。

原文:Replica management in Elasticsearch Serverless - Elasticsearch Labs

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐