Elasticsearch 深度分页查询问题处理方案
背景
在 Elasticsearch 中,分页查询是常见的需求,尤其是在处理大量数据时。为了提高查询效率,Elasticsearch 提供了多种分页方案,适用于不同的场景。笔者根据实际的使用情况整理了相关方案。
方案
Elasticsearch 分页查询方式主要有如下 3 种,现给出结论。
查询方案 | 用法限制 | 适用场景 |
---|---|---|
from+size 查询 |
支持随机跳转不同分页,不超过 max_result_window 值 |
小数据范围查询 |
search_after 查询 |
仅支持向后翻页,可以超过 max_result_window 值 |
APP向下滑动查看新闻 |
scroll 查询 |
大数据量分页查询,但数据实时性不高 | 批量大数据、日志导出 |
from+size 小数据范围查询
from
参数指定从结果集中的第几条数据开始返回数据,size
参数指定返回数据的总量。假设我们有一个索引 my_index
,并希望查询名字为 “梦想歌” 的文档,且返回 10 条数据。可以使用如下查询:
1 | GET my_index/_search |
当 from+size
超过 max_result_window
值(默认为 10000 条),返回报错。
1 | GET /my_index/_search |
报错内容如下。
1 | { |
结论:from+size
查询只适合小数据范围的查询,超过 max_result_window
时,无法继续分页查询。
search_after 向后滚动翻页
search_after
是一个基于排序字段的分页方式,适用于需要按特定顺序(例如时间戳、ID)查询大量数据。与传统的分页不同,它不使用 from
参数,而是基于上一页的最后一条记录的排序值进行查询。为了保证分页过程中的数据一致性,search_after
通常需要配合 Point In Time
(PIT)一起使用。
在执行分页查询之前,首先需要创建一个 PIT 来固定查询时刻的数据快照,避免查询过程中数据的变化。
1 | POST /my_index/_pit?keep_alive=1m # 滚动视图保留 1 分钟 |
这个请求会返回一个 PIT ID
,如下所示:
1 | { |
假设你要按 timestamp
字段升序排序,并且已经获取了第一页的最后一条文档的 timestamp
值为 1633036800000
,可以使用如下的 search_after
查询获取下一页数据:
1 | GET /_search |
在完成所有分页查询后,调用 DELETE 请求来删除 PIT,从而释放相应的资源。
1 | DELETE /_pit |
结论:search_after
查询无论数据量有多大,都可以不受 max_result_window
限制进行分页(严谨的说法是单次查询不能超过限制)。缺点是仅支持向后分页(不能向前翻页),需要在每次分页时提供上一页的排序值,并确保排序字段的唯一性。
scroll 查询
scroll
查询用于批量获取大量数据,尤其是在需要一次性遍历整个索引或较大数据集时,它比传统的分页查询更高效。
scroll
通过维护一个滚动上下文来支持分页的,而不是重新计算分页的位置。每次返回的结果包含一个新的 scroll_id
,我们可以用它来继续获取下一批数据。
1 | POST /my_index/_search?scroll=1m |
返回值示例如下,其中,_scroll_id
用于标识当前查询的滚动上下文。
1 | { |
使用返回的 _scroll_id
来请求后续数据。
1 | POST /_search/scroll |
所有数据获取完毕后,应该通过 Scroll API 来释放资源。
1 | DELETE /_search/scroll |
结论:scroll
查询适合用于批量数据导出、日志分析等场景,但是查询的过程中,返回的数据是非实时的,并且数据量较大时,需要有足够的堆内存空间来保留上下文。
总结
对于小数据量的分页查询,可以使用 from+size 查询。
当分页结果超过 10000 条结果时,则推荐使用 search_after 查询。
不推荐使用 scroll 查询进行深度分页,因为实时性不高,对资源要求高。