Elasticsearch 的批量查询阶段如何在大规模场景下提升搜索性能

作者:来自 Elastic Ben ChaplinLuca Cavanna

批量查询阶段可以通过减少传输开销,并在集群中更好地分配归并(reduction)工作量,将搜索执行时间缩短一半。

批量查询阶段已在 Elasticsearch Serverless 中上线,并于 Elasticsearch 9.5.0 正式发布,它改变了搜索请求在集群中的协调方式。现在,协调节点会将发往每个数据节点的所有查询批量合并为一个请求,而不是针对每个 shard 分别发送一个传输请求。这样,数据节点就可以自行执行部分结果归并,而不必将所有结果都发送回协调节点。在受协调开销限制的工作负载中,这可以将搜索执行时间缩短一半。

Elasticsearch 如何执行搜索

先从一些 Elasticsearch 基础知识开始。当一个搜索请求到达 Elasticsearch 节点时,该节点就会成为此次搜索的_协调节点(coordinating node)。默认情况下,任何 Elasticsearch 节点都可以充当协调节点。协调节点会根据搜索所指定的索引,确定需要搜索哪些 shards。承载这些 shards 的节点称为_数据节点(data node)。协调节点本身也可以同时是数据节点,因为它可能恰好承载此次搜索所涉及的 shards。

Elasticsearch 主要通过两个阶段执行搜索:查询阶段(query phase) ,也称为 scatter phase ,以及_获取阶段(fetch phase)_,也称为 gather phase 。查询阶段负责访问数据节点,并在每个 shard 上执行查询。每个 shard 会返回一组文档 ID(只有 ID,不包含数据)以及每个文档对应的 score。然后对这些结果进行归并。接下来,获取阶段再次访问数据节点,获取文档的 _source(即实际数据)。

更多阅读:"Elasticsearch:彻底理解 Elasticsearch 数据操作"

那么,Elasticsearch 中的归并(reduction)究竟是什么?归并就是将查询阶段来自各个 shard 的结果转换为一个合并后的结果并返回给客户端。假设一个搜索请求要求根据相关性 score 获取一个包含 5 个文档的结果,而目标索引有 3 个 shards。Elasticsearch 必须先从每个目标 shard 获取排名前 5 的 hits。为什么?因为某个 shard 可能包含全局排名前 5 的所有文档,更常见的情况是,排名前 5 的文档分布在多个 shards 中。

如果搜索了 3 个 shards,那么查询阶段结束后,协调节点将得到 15 个 (docID, score) 对。这些结果随后会被归并:保留 score 最高的前 5 个文档,丢弃其余结果。然后,获取阶段再次访问数据节点,获取这些文档的 _source 数据,最后由 Elasticsearch 将结果返回给客户端。

yaml 复制代码
`

1.  shard1_results = [(id: 231, score: 0.871), (id: 445, score: 0.812), (id: 88, score: 0.754), (id: 312, score: 0.701), (id: 567, score: 0.643)]

3.  shard2_results = [(id: 847, score: 0.921), (id: 76,  score: 0.843), (id: 125, score: 0.783), (id: 438, score: 0.729), (id: 590, score: 0.668)]

5.  shard3_results = [(id: 512, score: 0.887), (id: 289, score: 0.798), (id: 74,  score: 0.741), (id: 631, score: 0.682), (id: 405, score: 0.619)]

7.  // 从上面的结果中取 score 最高的前 5 个(这就是"归并")
8.  reduced_result = [(id: 847, score: 0.921), (id: 512, score: 0.887), (id: 231, score: 0.871), (id: 76, score: 0.843), (id: 445, score: 0.812)]

`AI写代码

什么是批量查询阶段?

不使用批量处理时(shard 扇出)

要理解批量查询阶段,首先需要了解没有批量处理时查询阶段是如何工作的。下面的示意图展示了一个包含 3 个节点和 12 个索引 shards 的 Elasticsearch 集群。假设客户端的搜索请求到达 Node 1,那么 Node 1 就会成为此次搜索的协调节点。

协调节点首先会向所有 shards 进行_扇出(fan-out)_,在每个 shard 上执行客户端的查询。每个 shard 查询都通过一个传输请求(transport request)来完成,这是 Elasticsearch 节点之间进行通信所使用的协议。请注意,在这个示意图中,每个 shard 都有自己独立的传输请求。Node 1 自己也承载了一些 shards,因此查询这些 shards 不需要网络请求。

完成 shards 查询后,协调节点必须按照前一节所述对这些结果进行归并。此时,协调节点持有 12 个 shard 的结果。将它们全部归并之后,协调节点就可以进入获取阶段,然后向客户端返回响应。

使用批量处理

那么,批量查询阶段改变了什么?批量查询阶段首先在发送 shard 查询传输请求之前发挥作用。

现在,协调节点会按照每个数据节点,将需要查询的 shards 进行批量处理,然后一次性向该数据节点请求这些 shards。

请注意,Node 2 只收到一个传输请求,Node 3 也只收到一个传输请求。这是批量查询阶段带来的第一个变化。每个传输请求都会产生一定的开销,因此仅这一项改变就能带来第一次性能提升,降低延迟,并减少用于处理传输开销的 CPU 周期。

批量查询阶段带来的第二个变化与 shard 结果的归并有关。现在,数据节点本身就可以执行部分归并。例如,在查询完 shards 2、5、8 和 11 后,Node 2 会直接对这些结果进行归并。协调节点收到来自 Node 2 和 Node 3 的部分归并结果后,再执行最终归并。

这就是批量查询阶段带来的第二项重大改进:将归并工作分散到各个数据节点上。这样可以降低协调节点的内存压力,我们将在后面的基准测试部分看到这一点的实际测量结果。

批量处理的优势

*扇出(fan-out)*是 Elasticsearch 长期以来执行搜索的方式。它有自己的优势:每个 shard 都对应一个独立请求,可以独立返回,也可以独立于其他请求进行重试。但当涉及大量 shards 时,每个 shard 请求都会产生额外开销,因为协调节点与数据节点之间需要进行大量往返通信。

此外,协调节点掌握的信息不足以决定向每个数据节点发送请求的最佳节奏。默认情况下,它会向每个数据节点最多同时发送 5 个请求。5 是一个有些 "神奇" 的数字:它允许一定程度的并行处理,同时又能避免单个查询占用整个数据节点。实际上,如果一次搜索涉及的所有 shard 请求都以一个批次的形式发送给每个数据节点,那么数据节点就可以根据自身的内部状态动态调整处理节奏。

批量查询阶段带来了多项优势,包括:

  • 提高效率:由于往返次数减少,花费在传输开销上的 CPU 更少,并且通过传输层传输的字节数也更少。

  • 分散归并负载:以前归并工作主要由协调节点承担,容易成为瓶颈;现在数据节点也可以分担这部分工作。

  • 更好地利用资源:我们可能可以更充分地利用数据节点的 CPU。

现在,对于涉及大量 shards 的搜索,平均搜索请求可以更快地得到处理,同时具有更低的延迟和更高的吞吐量。

批量查询阶段基准测试:延迟和内存使用

为了衡量批量查询阶段带来的优势,我们进行了几项基准测试。第一项测试展示了减少传输开销所带来的收益。这项基准测试基于 "many-shards-quantitative"nightly benchmark 构建。

测试运行在一个包含 3 个节点的 Elasticsearch 集群上,分别运行针对 1,000、5,000 和 20,000 个 shards 的搜索批次。查询使用的是 match_all 查询,并设置 size: 0。这意味着在 shards 本身执行查询实际上几乎不需要进行任何工作。

这项基准测试旨在隔离搜索在集群中的协调工作。

我们分别在开启和关闭批量查询阶段的情况下运行了相同的基准测试,通过切换集群设置 search.batched_query_phase 来控制是否启用该功能。

在 5,000 个 shards 的情况下,启用批量查询阶段后,搜索速度提升了 2 倍 。到了 20,000 个 shards,速度提升达到 2.2 倍

这些结果代表了实际工作负载能够获得的收益上限;任何发生在 shard 层面的实际查询工作都会稀释整体收益。不过,这些性能提升是真实存在的,你的查询所涉及的_协调工作_也会获得如上所示的收益。

接下来,我们测试了批量查询阶段在处理大规模归并时带来的优势。在这项基准测试中,我们针对名为 http_logs 的数据集运行了一个大型 terms 聚合。该数据集可以在我们的 rally-tracks 仓库中找到。

这个数据集包含 2.47 亿个文档,我们将其索引到 7 个索引中,每个索引包含 100 个 shards,同样运行在一个三节点 Elasticsearch 集群上。

我们运行了一个针对 clientip 字段的单次 terms 聚合,设置 size: 1000shard_size: 50000

这意味着我们要求返回排名前 1,000 的 terms,但每个 shard 都会返回 50,000 个 buckets,然后再将这些 buckets 归并为最终的 1,000 个结果。

_内存使用量_通过一种称为*熔断器(circuit breaker)*的机制进行测量,该机制负责统计 Elasticsearch 中的内存使用情况。这个字段可以在 /_nodes/stats 响应中的 nodes.<node_id>.breakers.request.estimated_size_in_bytes 找到。它用于估算当前正在处理的搜索请求所占用的内存。

内存基准测试展示了归并工作现在是如何在整个集群中分散的。蓝色表示 search.batched_query_phase: false 时的内存使用情况。可以看到,elasticsearch-0 是协调节点,因为全部归并负载都由它自己承担。

而当 search.batched_query_phase: true 时,红色所示的情况就不同了。elasticsearch-1 是协调节点,但它使用的内存要少得多。这是因为数据节点 elasticsearch-0elasticsearch-2 会自行执行归并,在将结果返回给协调节点之前先对结果进行归并。

总结

按照数据节点对 shard 查询进行批量处理,可以减少传输开销,并允许数据节点自行对结果进行部分归并。这样,对于涉及大量 shards 的搜索,可以降低延迟;对于归并密集型工作负载,则可以更好地在集群中分配工作。

此外,这也为未来进一步优化 shard 并发处理打开了空间,我们非常期待继续探索这些方向。

原文:Elasticsearch batched query phase: Improve search performance | Elasticsearch Labs

相关推荐
Ramboooooooo4 小时前
SkyWalking-10.4.0 Docker + Nacos + Elasticsearch 生产级部署手册
elasticsearch·docker·skywalking
互联网中的一颗神经元14 小时前
04 — 安全撤销:改错了怎么退回去
大数据·安全·elasticsearch
Elasticsearch19 小时前
Elasticsearch:列式索引模式 - Columnar index mode
elasticsearch
Elasticsearch1 天前
从 CrashLoopBackOff 到根本原因,只需几秒:使用 Elastic Observability 自动化 20 分钟的 Kubernetes 调查流
elasticsearch
互联网中的一颗神经元2 天前
09 — .git 地图:打开那个隐藏文件夹
大数据·git·elasticsearch
独行侠影a2 天前
Elasticsearch海量数据查询优化:深度分页与聚合查询提速方案
大数据·elasticsearch·搜索引擎
liu_sir_2 天前
3572_a16_3566_A11_A9等解决机器接电池或者插DC电源上电的时候不要立马开机,要通过电源按键按下之后才开机
大数据·elasticsearch·搜索引擎
qingyulee2 天前
git常用指令
大数据·git·elasticsearch
Elasticsearch3 天前
从工具采购到平台架构:重新思考应对机器速度威胁的 SOC
elasticsearch