Elasticsearch Search After分页查询所有数据的实现方式
作者:二六八
search_after 分页的方式是根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。但是需要注意,因为每一页的数据依赖于上一页最后一条数据,所以无法跳页请求。
为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,其实使用业务层的 id 也可以。
上面的请求会为每一个文档返回一个包含sort排序值的数组。这些sort排序值可以被用于 search_after 参数里以便抓取下一页的数据。比如,我们可以使用最后的一个文档的sort排序值,将它传递给 search_after 参数:
注意:当我们使用search_after时,from值必须设置为0或者-1。
search_after缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序。它与滚动API非常相似,但与它不同,search_after参数是无状态的,它始终针对最新版本的搜索器进行解析。因此,排序顺序可能会在步行期间发生变化,具体取决于索引的更新和删除。
1. search_after 查询
search_after 查询定义与实战案例
search_after 查询本质:使用前一页中的一组排序值来检索匹配的下一页。
前置条件:使用 search_after 要求后续的多个请求返回与第一次查询相同的排序结果序列。也就是说,即便在后续翻页的过程中,可能会有新数据写入等操作,但这些操作不会对原有结果集构成影响。
如何实现呢?
可以创建一个时间点 Point In Time(PIT)保障搜索过程中保留特定事件点的索引状态。
- Point In Time(PIT)是 Elasticsearch 7.10 版本之后才有的新特性。
- PIT的本质:存储索引数据状态的轻量级视图。
如下示例能很好的解读 PIT 视图的内涵。
创建 PIT
POST kibana_sample_data_logs/_pit?keep_alive=1m
获取数据量 14074
POST kibana_sample_data_logs/_count
新增一条数据
POST kibana_sample_data_logs/_doc/14075 { "test":"just testing" }
数据总量为 14075
POST kibana_sample_data_logs/_count
查询PIT,数据依然是14074,说明走的是之前时间点的视图的统计
POST /_search
{ "track_total_hits": true, "query": { "match_all": {} }, "pit": { "id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEN3RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA" } }
有了 PIT,search_after 的后续查询都是基于 PIT 视图进行,能有效保障数据的一致性。
search_after 分页查询可以简单概括为如下几个步骤。
Step 1: 创建 PIT
步骤 1:创建 PIT 视图,这是前置条件不能省。
POST kibana_sample_data_logs/_pit?keep_alive=5m
返回结果如下:
{ "id" : "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA" }
keep_alive=5m,类似scroll的参数,代表视图保留时间是 5 分钟。
超过 5 分钟执行会报错如下:
{ "type": "search_context_missing_exception", "reason": "No search context found for id [91600]" }
Step 2: 创建基础查询
步骤 2:创建基础查询语句,这里要设置翻页的条件。
GET /_search
{ "size":10, "query": { "match" : { "host" : "elastic" } }, "pit": { "id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA", "keep_alive": "1m" }, "sort": [ {"response.keyword": "asc"} ] }
设置了PIT,检索时候就不需要再指定索引。
id 是基于步骤1 返回的 id 值。
排序 sort 指的是:按照哪个关键字排序。
在每个返回文档的最后,会有两个结果值,如下所示:
{ "sort": [ "200", 4 ] }
其中,“200”就是我们指定的排序方式:基于 {“response.keyword”: “asc”} 升序排列。
而 4 代表什么含义呢?
4 代表——隐含的排序值,是基于_shard_doc 的升序排序方式。
官方文档把这种隐含的字段叫做:tiebreaker (决胜字段),tiebreaker 等价于_shard_doc。
tiebreaker 本质含义:每个文档的唯一值,确保分页不会丢失或者分页结果数据出现重复(相同页重复或跨页重复)。
step 3 : 开始翻页
步骤3:实现后续翻页。
GET /_search
{ "size": 10, "query": { "match" : { "host" : "elastic" } }, "pit": { "id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA", "keep_alive": "1m" }, "sort": [ {"response.keyword": "asc"} ], "search_after": [ "200", 4 ] }
后续翻页都需要借助 search_after 指定前一页的最后一个文档的 sort 字段值。
如下代码所示:
{ "search_after": [ "200", 4 ] }
显然,search_after 查询仅支持向后翻页。
search_after 查询优缺点及适用场景
search_after 优点
- 不严格受制于 max_result_window,可以无限制往后翻页。
- ps:不严格含义:单次请求值不能超过 max_result_window;但总翻页结果集可以超过。
search_after 缺点
- 只支持向后翻页,不支持随机翻页。
search_after 适用场景
- 类似:今日头条分页搜索 https://m.toutiao.com/search
- 不支持随机翻页,更适合手机端应用的场景。
2. Scroll 遍历查询
Scroll 遍历查询定义与实战案例
相比于 From + size 和 search_after 返回一页数据,Scroll API 可用于从单个搜索请求中检索大量结果(甚至所有结果),其方式与传统数据库中游标(cursor)类似。
如果把 From + size 和 search_after 两种请求看做近实时的请求处理方式,那么 scroll 滚动遍历查询显然是非实时的。数据量大的时候,响应时间可能会比较长。
scroll 核心执行步骤如下:
步骤 1:指定检索语句同时设置 scroll 上下文保留时间。
实际上,scroll 已默认包含了 search_after 的PIT 的视图或快照功能。
从 Scroll 请求返回的结果反映了发出初始搜索请求时索引的状态,类似在那一个时刻做了快照。随后对文档的更改(写入、更新或删除)只会影响以后的搜索请求。
POST kibana_sample_data_logs/_search?scroll=3m { "size": 100, "query": { "match": { "host": "elastic" } } }
步骤 2:向后翻页继续获取数据,直到没有要返回的结果为止。
POST _search/scroll { "scroll" : "3m", "scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkY4UkIwZWtlU2d1OTdTUjRIbzVXdHcAAAAAAAGmkBZ0bVM5YUxMX1R1Nkd1VkNiaGhZSWNn" }
scroll_id 值是步骤 1 返回的结果值。
Scroll 遍历查询优缺点及适用场景
scroll 查询优点
- 支持全量遍历。
- ps:单次遍历的 size 值也不能超过 max_result_window 大小。
scroll 查询缺点
- 响应时间非实时。
- 保留上下文需要足够的堆内存空间。
scroll 查询适用场景
- 全量或数据量很大时遍历结果数据,而非分页查询。
- 官方文档强调:不再建议使用scroll API进行深度分页。如果要分页检索超过 Top 10,000+ 结果时,推荐使用:PIT + search_after。
总结
From+ size
:需要随机跳转不同分页(类似主流搜索引擎)、Top 10000 条数据之内分页显示场景。search_after
:仅需要向后翻页的场景及超过Top 10000 数据需要分页场景。Scroll
:需要遍历全量数据场景 。max_result_window
:调大治标不治本,不建议调过大。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。