Elasticsearch搜索机制与分页优化策略
引言
Elasticsearch的搜索性能依赖于其分布式架构与混合索引设计:
- 两阶段查询:协调节点通过Query阶段并行筛选分片结果(基于文档ID与排序值),再经Fetch阶段精准拉取全量数据;
- 混合索引加速:BKD树(数值/日期字段)实现高效范围查询(
O(log N)
),倒排索引(字符串字段)支持快速关键词匹配; - 海量数据分页:突破
max_result_window
默认限制需权衡内存开销,而Scroll API(批量拉取)与Search After(动态分页)分别适用于离线分析与实时场景。
全文解析搜索链路核心逻辑,为高并发场景提供性能优化方向。
文档搜索的过程
Query阶段
- Client 将请求发送到任意节点 node,此时 node 节点就是协调节点(coordinating node)。
- 协调节点进行分词等操作后,去查询所有的分片(主分片和副本分片选择一个)。
- 所有分片将满足条件的文档 ID、排序字段的值等信息返回给协调节点。
- 协调节点重新进行排序,截取数据后,获取到真正需要返回的数据的 id。
Fetch阶段
- 协调节点再次请求对应的分片 (此时有 id 了,可以直接定位到对应分片)。
- 获取到全量数据,返回给 Client。
多条件搜索过程
{"query": {"bool": {"must": [{"match": {"brand": "BrandA"}},{"range": {"price": {"lt": 800}}},{"term": {"available": true}}]}}
}
协调节点会将所有查询条件并行的分发到所有的数据节点,每个节点在其本地的分片上执行词项查找。最后得到多个集合,对多个集合取并集得出满足条件的文档ID,在进行Fetch阶段操作。
注:index.max_result_window默认10000,搜索请求返回文档数量限制,返回文档数超过配置则报错。
文档搜索是否会跳过Fetch阶段?
是否需要进入 Fetch 阶段取决于查询请求的具体配置。如果查询请求只需要文档ID、特定字段或进行聚合计算,则可以跳过 Fetch 阶段。
范围查询为什么会高效?
数值类型、日期类型字段并不是使用倒排索引结构存储的,而是使用BKD树结构存储的。这种结构特别适合处理范围查询和排序操作,因为它能够高效地进行数值范围搜索、排序以及聚合操作。
字符串类型字段是使用倒排索引结构存储的。
什么是BKD树
K-D-B树是K-D树与B树的结合,而B-K-D树是基于K-D-B树设计的。
高效的原因
- 采用BKD数据结构存储数值、日期类型,BKD又有B树的影子所以范围查询效率高。
- 分布式架构设计,可以同时在多个分片上进行范围查询,最后在协调节点进行汇总。
如何处理返回大结果集的搜索请求?
Elasticsearch 会通过index.max_result_window配置(默认10000)限制返回结果集大小,在分页查询中from + size超过配置也会报错。
调大index.max_result_window
配置
- 优点:可以允许 Elasticsearch 返回更多的结果,从而支持更大范围的分页查询。
- 缺点:每次查询返回的结果集变大,这会增加 Elasticsearch 的内存占用。处理查询时花费更多的时间,特别是在数据量非常大的情况下,查询效率可能会明显下降。
使用Scroll Api查询
每次查询都会返回scroll_id字段,下次查询时把scroll_id传进去继续查询,直到hits为空则不在进行查询,hits是实际返回的数据。
- 优点:适用于从单个搜索请求中检索大量数据,类似于数据库中的游标,可以遍历所有结果;性能较稳定,尤其是在大规模数据集上。
- 缺点:不适合实时性要求高的场景,因为每次执行 scroll 操作时,数据快照会固定在查询初始时的状态,不能实时反映数据的更新。
使用Search After查询
下次查询时,需要提供上一页最后一条记录的排序信息。如果排序值不唯一下次查询时会产生重复数据。
- 优点:适用于深度分页,能实现实时性要求较高的查询,每次请求都是独立的,并能处理数据的实时更新。从上一个查询结果的最后一个文档开始检索,避免了深分页时的性能问题。
- 缺点:需要有一个唯一且可排序的字段来实现分页,可能会对查询性能有影响,并且只能是一页一页的向后翻。
感谢您的阅读!如果文章中有任何问题或不足之处,欢迎及时指出,您的反馈将帮助我不断改进与完善。期待与您共同探讨技术,共同进步!