ElasticSearch三种分页对比

1、from-size浅分页

        from表示初始偏移量,默认为0。size表示单页返回最大文档条数,默认为10。假设我们在有5个主分片的索引中搜索,查询第一页数据,即前10条数据,那么es会从每个分片中生成排序好的结果,取出前10条,然后返回给请求节点,请求节点再将这50条记录再次排序选出前10条。

ps:使用ElasticSearch的form+size方式分页时,当查询记录超过10000时,会保如下错:

Result window is too large, from + size must be less than or equal to: [10000] but was [10025]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level parameter.

查询文档可知:

index.max_result_window The maximum value of from + size for searches to this index. Defaults to 10000. Search requests take heap memory and time proportional to from + size and this limits that memory. See Scroll or Search After for a more efficient alternative to raising this.

解决办法: 

// 修改最大条数限制
PUT index/_settings
{
    "index":{

        "max_result_window":200000
    }
}

             但是如果请求的是第1000页的数据,即第10001至第10010条数据。那么es会从5个分片中取出各个分片顶端的10010条数据,然后将这总共50050条数据返回给请求节点,请求节点再次对这50050条数据进行一次全局排序,取出第10001至10010条数据,抛弃50040条数据,而且每次翻页都需要重新排序,这就造成了很大的浪费。

        在分布式系统中排序的花费会随着分页的深度而成倍增长,如果数据特别大对CPU和内存的消耗会非常巨大甚至会导致OutOfMemory,所以这也是为什么网络搜索引擎不能返回多于10000个结果的原因。ES使用index.max_result_window:10000作为保护措施 ,即默认 from + size 不能超过10000,虽然这个参数可以动态修改,也可以在配置文件配置,但是不推荐这么做。

2、scroll方式

Request body search | Elasticsearch Guide [8.3] | Elastic

http://lxwei.github.io/posts/使用scroll实现Elasticsearch数据遍历和深度分页.html

        可以看出上述from/size的分页方式当分页深度很深时是存在问题的,因此很多业务直接限制了过深的分页。但是某些业务是需要遍历全量数据的,例如把某个索引下的文档reindex到一个新的不同配置的索引下,再例如消息的群发等。

        scroll API 就适用于这种期望一次请求返回大量数据甚至全部数据的情况。为了更好的说明scroll的工作方式,首先说明一下ES搜索的内部执行原理,默认的query then fetch类型检索分为query和fetch两个阶段,query阶段较为轻量级,由每个shard返回排序后的前N各文档的id和排序需要的score值,然后进行全局排序,fetch阶段再去对应分片取符合条件的全部文档,以减小网络开销。

        scroll API就利用这个特点,使用scroll方式做检索分为两个阶段,首次查询和后续查询。首次查询时,会对按查询条件query的结果做一个快照,并指定这个快照的维持时间(见:Keeping the search context alive),返回结果包含一个scroll_id。在后续查询时,仅需要此scroll_id即可找到上次查询的退出地点,接着取出下一页的值,并返回新的scroll_id用于接下来的查询。当某次返回的结果[hits][hits]为空,即返回了所有的结果时,scroll查询终止。因为采用了这种快照的方式,在首次查询结束后,对ES里文档的(更新、建索引、删除)都不会影响查询的结果,也即对实时性要求高的查询并不适合使用scroll。

 ps:

  1. scroll首次查询设置的一分钟,不是指整个查询限时为1分钟,而是到下一次查询还有1分钟的时间。每次查询都会指定新的维持时间,并重新计时。如果超时,清除context此次查询失效,需重新执行首次查询。
  2. 后续请求时url中不应该有index和type,这些都在初始请求中,后续只需要使用scroll_id和scroll两个参数即可,其中scroll_id可以放在url中也可以放在请求体中。
  3. 当只需要获得全部数据而不考虑数据排序时,可以使用 '_id'(见下代码)作为排序依据,es对此做了优化,会很大的提高速度。
  4. 需要将每次将返回的scroll_id用于下次查询。
  5. 清除scroll,虽然scroll的有效时间到后,context会清除,但是如果能在使用完后手动删除,可以提早释放资源,降低ES的负担。
//首次查询
curl -u $user:$password -H "Content-Type:application/json" -XGET "http://$ip:$port/$index/type/_search?scroll=1m' -d ' //scroll=1m 即快照有效时间为1分钟
{
    "query":{
        "match":{
            "name":"LEMON"
        },
        "size":1000 //可以指定size大小,就是每页的文档数,当回传到没有数据时,仍会返回200成功,只是hits里的hits是空List
    }
}

//后续查询
curl -XGET 'http://$ip:$port/$index/_search/scroll' -d'
{
    "scroll":"1m",
    "scroll_id":"c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1"
}

//手动释放多个scroll
curl -XDELETE /_search/scroll
{
    "scroll_id":[
        "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
        "DnF1ZXJ5VGhlbkZldGNoBQAAAAAajZkTGlIYkJWZDFhQQAAAAMWFB"
    ]
}

//释放所有
DELETE /_search/scroll/_all

3、search after方式

        from/size的方式在用于深分页代价高昂,scroll可以高效的深分页,但是维持上下文开销较大,而且存在实时性的问题。search after参数通过设置一个活动游标(a live cursor),这种方法的思想和scroll很像,只是在首次query得到查询结果排序后,并不保存这个快照,而是只保存用于排序的最后一个的值。在下次查询时,每个分片只返回这个排序值之后的值,因此需要使用文档里唯一的字段作为排序依据,推荐将_id字段复制到另一个字段,并enable doc_value(tie_breaker_id)。如下图,红色部分为当次查询总排序后,在分片中取的数据。结合图片可以看出,不同于from/size方式,search_after每次全局排序的量不会随着页数增加而增加,而且每次查询均是实时的。

ps:from值需要设置为0或-1

GET index/_search
{
    "size":10,
    "query":{
        "match":{
            "name":"LEMON"
        }
    },
    "search_after":[
        "123456789" //上页末尾文档排序依据值
    ],
    "sort":[
        {
            "tie_breaker_id":"asc"
        }
    ]
}

4、id与_uid

区别:_id在特定query中可以被获取,如term, terms, query_string,但在聚合统计、自定义脚本以及排序时,不可以被使用,此时只能通过_uid来代替

联系:_uid实际上是index下的type与_id拼接,格式为_uid=type#_id

  1. 6.0版本之前,可以用 _uid去代替 _id,_id是不支持排序的,因为_id默认是not index的,而_uid其实是一个 _type + _id的字符串。

  2. 6.0版本之后,_id会被建索引,也可以支持aggregate和sort了,_uid不再单独存在,而是_id的别名。如果要按_id排序,不建议直接用_id,而是可以拷贝_id值到另一个新字段,新字段启用docvalue,这样可以节省内存使用
    1. 如果_id是123456789,并且也搜索了123。。,它仍然会匹配该文档并返回在它后面找到的结果。此字段上的文档值被禁用,因此对其进行排序需要在内存中加载大量数据。相反,建议在另一个启用了doc value的字段中复制id字段的内容,并使用此新字段作为排序的分条符。

5、form_size / scroll / search_after 性能比较

假设执行如下查询:

GET index/_search
{
    "query":{
        "match":{
            "name":"LEMON"
        }
    }
}

es分页性能对比表

【1 - 10】

【49000 - 49010】

【 99000 - 99010】

form_size8ms30ms117ms
scroll7ms66ms360ms
search_after5ms8ms7ms

6、总结

实时性

跳页查询

应用场景

from/pageYY列表展示、浅分页
scrollNN

查询大量(全部)数据、reindex

search_afterYN滚动下拉

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值