前言
当数据量较大无法一次性返回所有数据给前端时,往往需要对结果进行分页查询。 ElasticSearch分页实现有三种方式:from+size,searchAfter和scroll,这三种方式各有有优缺点,下面将依次进行介绍。
from+size
from和size是es search API的参数,from定义了起始位置,默认值为0,size则表示需要返回的结果数量。from和size一起则圈定了一个结果集。
GET /_search
{
"from": 5,
"size": 20,
"query": {
"match": {
"user.id": "kimchy"
}
}
}
from+size的方式的优点是使用简单,可以随机跳页,适用于结果集不超过10000个的情况。这种方式不适用于深度分页,在深度分页情况下会有性能问题。
es请求会跨越多个分片,每个分片都会产生自己的排序结果,这些结果需要集中进行排序以保证整体结果是正确的。
假设在有5个主分片的索引上进行搜索,当我们请求结果的第一页(结果从1到10),每一个分片会产生前10的结果,并且返回给协调节点,协调节点会对50个结果进行排序得到全部结果的前10个。
现在假设我们请求第1000页(结果从10001到10010),每一个分片会产生前10010的结果,协调节点会对50050个结果进行排序,丢弃掉50040个结果并得到最终的前10个。
可以看到,在深度分页情况下,内存和CPU的使用率会显著上升,进而影响搜索效率。因此默认情况下,使用from+size是不能对超过10000条的数据进行分页的。如果需要对超过10000条的数据进行分页,可以使用searchAfter。
searchAfter
你可以通过searchAfter来获取下一页,searchAfter需要带上上一页最后一个结果的sort value。
获取第一页结果时,提交一个带有sort的请求。
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"sort": [
{"timestamp": {"order": "asc"}}
]
}
search的返回结果数组中,每一个结果都有带有一个sort value。
{
"took" : 17,
"timed_out" : false,
"_shards" : ...,
"hits" : {
"total" : ...,
"max_score" : null,
"hits" : [
...
{
"_index" : "my-index-000001",
"_id" : "FaslK3QBySSL_rrj9zM5",
"_score" : null,
"_source" : ...,
"sort" : [
1623337212000
]
}
]
}
}
要获取下一页的结果,则需要将上面结果集中的最后一个结果的sort value作为searchAfter的参数。可以通过重复searchAfter请求不断获取下一页,如果有的话。使用searchAfter获取下一页结果要求多个请求的query条件和sort值都必须是相同的。
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"sort": [
{"timestamp": {"order": "asc"}}
],
"search_after": [
1623337212000
]
}
scroll api
es官方文档提示不推荐使用scroll api来进行深分页,如果是深分页的场景,推荐使用search after,scroll则适用于通过批任务全量导出查询结果的场景。
scroll就是把查询结果缓存一段时间。如scroll=1m,就是在下一个请求到达前将查询结果缓存5分钟,返回值里面包含一个scroll_id。下次请求带上上一次请求返回的scroll_id,就可以找到对应的缓存结果。
POST /my-index-000001/_search?scroll=3m
{
"size": 100,
"query": {
"match": {
"message": "foo"
}
}
}
POST /_search/scroll
{
"scroll" : "3m",
"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoAwAAAAAABE74FlZmS2MzSzRjVGFlWmhJNVdEd3N0REEAAAAAAAHT6hZJT205MzczWlFxdUxINXprd05MenFnAAAAAAAB0-kWSU9tOTM3M1pRcXVMSDV6a3dOTHpxZw=="
}
每一个带有scroll参数的scroll请求都会设置一个新的过期时间,如果一个scroll请求没有带有scroll参数,那么这个scroll请求就会释放之前缓存的结果。