zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

ElasticSearch深浅分页查询及原理

elasticsearch原理 查询 分页 深浅
2023-09-27 14:28:03 时间

一、from-size(深分页)

1、分页原理

假设有8分片,查询到第1000页数据,from =1000 size=100,es每次会从取出每个分片取1000*100+100=11w条数据,自然每个分片都会存储这11w条数据,然后再发给协调节点做排序后,而协调节点就是面临处理8*11w=88w条的巨大压力

随着from页码的不断增加,es从每个分片获取的数据量也就越来越大,自然越来越慢,于es所在服务器和应用系统都带来不小压力,甚至出现内存溢出风险。因此es默认使用10000作为最大查询值,超过此值,推荐使用scroll游标来滚动查询。

2、踩坑指南

如果初次使用,不注意的话,当超过10000时候,查询会报如下异常,

Result window is too large, from + size must be less than or equal to: [10000] but was [22020]. 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 setting

当然10000也可以调整,,如最大上限调整为800000

PUT my_index/_settings
{"index.max_result_window":"800000"}

二、滚动查询(浅分页)

1、基本原理

总体而言,scroll查询顾名思义-滚动查询,类似于关系型数据库oracle的cursor游标。初次查询时,将所有符合搜索条件的doc _id集排序后存储在上下文,类似于快照。在之后每次遍历时,带着上次的_scroll_id从这个快照里取size数据,从而从各自_scroll_id对应的分片获取数据。

那么有人说假设一次搜索满足条件的有1000w个doc _id,甚至更多的时候,会不会撑爆内存。这个不用担心,es使用了非常紧凑的数据结构和压缩算法来存放这些ID,占用的内存不会太多。


详细的说,初次查询大致分为两个阶段

Query阶段:将每个shard将命中的结果( doc_id和_score) 按照 _score 顺序在上下文中创建一个优先队列快照,并通过scroll_id指向它,lastEmittedDoc指向上次访问的位置,最后将TOP(size)的doc id返回给协调节点。

Fetch阶段:协调节点将各个shard返回的结果再进行合并排序,最后通过doc_id查找返回结果的全量数据。之后更新各个分片上的上下文。

初次之后查找数据,在Query阶段通过scroll_id找到对应的快照,然后用lastEmittedDoc将原来的查询语句添加bool查询条件**( >=lastEmitted.doc + 1)**,在快照中找数据。

scroll_id也不是固定的,scroll_id其中保留了shard信息,假如scroll查询语句需要路由到16个shard上查。scroll_id会比较长,记录这16个shard。有可能从开始到完成都需要路由到这16个shard,shard_id就不会变化。也有可能随着不断进行scroll,需要路由到的shard越来越少,shard_id也会越来越短,随之变化。

2、操作步骤

(1)初始化scroll缓存

初次请求,要在url中的search后加上scroll=2m,这个scroll=2m(2m代表2分钟),是缓存时间,客户端可以根据查询数据数量自定义缓存的时间

POST my_index/_search?scroll=2m
{
  "from": 0,
  "size": 150,
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "bill_month": {
              "value": "2022-06",
              "boost": 1
            }
          }
        }
      ]
    }
  }
}

返回如下,会发现比不带scroll=2m,多返回了一个_scroll_id值为base64的字符串编码

 (2)使用_scroll_id继续请求

用每次得到的这个_scroll_id值,继续请求下一页(这个为了偏于展示,这里_scroll_id值写短些),每次请求最好都带上scroll=2m刷新过期时间,以防超时报错。

POST my_index/_search/scroll?scroll=2m

{
    "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAMQhbFkhrUXE3eE0tUy1LMkxRMDlhOGU1dEEA"
}
过了缓存时间会抛出如下异常
 Elasticsearch exception [type=search_context_missing_exception, reason=No search context found for id [3344636]

(3)清除scroll

这个_scroll_id在es的服务端是有缓存数量限制的,默认最大500,如果请求量大于这个值,会报错。因此除了自然过期之外,我们在处理完成本次请求后一般手动清除掉_scroll_id缓存,及早释放资源

DELETE /_search/scroll

{
	"scroll_id": "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAQ7QAFmtZT3luT3M0UUVDdE82MFp4QmtNcGcAAAAAADOE-hYyMDBxb29mb1J0bWdrS2ZwQ2FhSFZ3AAAAAAAxFi4WSGtRcTd4TS1TLUsyTFEwOWE4ZTV0QQ=="
}

参考:取回阶段 | Elasticsearch: 权威指南 | Elastic

参考:elasticsearch scroll查询的原理没太懂 - Elastic 中文社区

参考:es分页查询原理_喂喂喂_java的博客-CSDN博客_es 分页查询