ElasticSearch分页

593 阅读3分钟

前言

当数据量较大无法一次性返回所有数据给前端时,往往需要对结果进行分页查询。 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请求就会释放之前缓存的结果。