ES入门十五:分页的三驾马车【from+size、search after、scroll api】

本文介绍了在Elasticsearch中处理大数据分页的三种方法:from+size、searchafter以及scrollApi,讨论了它们的优缺点和适用场景,重点介绍了如何通过PointInTime和searchafter避免scrollApi的限制。
摘要由CSDN通过智能技术生成

从数据集中获取数据时分页是绕不开的操作,一下子从数据集中获取过多的数据可能会造成系统抖动、占用带宽等问题。特别是进行全文搜索时,用户只关心相关性最高的那个几个结果,从系统中拉取过多的数据等于浪费资源。

ES提供了3种分页方式:

  1. from + size: 最普通、简单的分页方式,但是会产生深分页的问题
  2. search after: 解决深分页的问题,但只能一页页地往下翻,不支持跳到指定页数
  3. scroll Api: 会创建数据快照,无法检索新写入的数据,适合对结果集进行遍历的时候

这3种方式的分页操作都有其优缺点,适合不同的场合使用。今天我们就来学习这3种分页方式,但除了学习这3种分页方式外,我们还会介绍ES新引入的特性:Point In Time,看看如何使用Point In Time+search after的方式来代替scroll Api进行大量数据的导出

from +size 分页操作与深分页问题

在我们检索数据时,系统会对数据相关性算分进行排序,然后默认返回前10条数据。我们可以使用from +size来指定获取哪些数据。其使用示例如下:

# 简单的分页操作
GET books/_search
{
  "from": 0, # 指定开始位置
  "size": 10, # 指定获取文档个数
  "query": {
    "match_all": {}
  }
}

如上示例,使用“from”指定获取数据的开始位置,使用“size”指定获取文档带的个数

但当我们将from设置大于10000或者size设置大于10001的时候,这个查询将会报错:

# 返回结果中的部分错误信息
......
"root_cause" : [
  {
    "type" : "illegal_argument_exception",
    "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. 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."
  }
],
"type" : "search_phase_execution_exception",
"reason" : "all shards failed",
"phase" : "query",
"grouped" : true,
......

从报错信息可以看出,我们要获取的数据集合太大了,系统拒绝了我们的请求。我们可以使用“index.max_result_window”配置项设置这个上限:

PUT books/_settings
{
  "index": {
    "max_result_window": 20000
  }
}

如上示例,我们设置了这个上限为20000。虽然使用这个配置有时候可以解决燃眉之急,但是这个上限设置过大的情况下会产生非常严重的问题,因为ES会存在深分页的问题

那为什么时深分页和为什么会产生深分页的问题那?
image.png
如上图,ES把数据保存在3个主分片中,当使用from=90 和size=10进行分页的时候,ES会从每个分片中分别获取100个文档,然后把这300个文档在汇聚到协调节点中进行排序,最后选出排序后的前100个文档,返回第90到99的文档

可以看到,当页数变大(发生了深分页)的时候,在每个分片中获取的数据越多,消耗的资源就越多。并且如果分片越多,汇聚到协调节点的数据越多,最终协调到协调节点的文档数为:shard_acount *(from + size)

search after

使用search after api可以避免产生深分页的问题,不过 search after 不支持跳转到指定页面,只能一页页的往下翻

使用 search after接口分为两步:

  1. 在sort中指定需要排序的字段,并且保证其值的唯一性(可以使用文档的id)
  2. 在下一次查询时,带上返回结果的最后一个文档的sort值进行访问

search after的使用示例如下:

# 第一次调用 search after
POST books/_search
{
  "size": 2,
  "query": { "match_all": {} },
  "sort": [
    { "price": "desc" },
    { "_id": "asc" }
  ]
}

# 返回结果
"hits" : [
  {
    "_id" : "6",
    "_source" : {
      "book_id" : "4ee82467",
      "price" : 20.9
    },
    "sort" : [20.9, "6"]
  },
  {
    "_id" : "1",
    "_source" : {
      "book_id" : "4ee82462",
      "price" : 19.9
    },
    "sort" : [19.9, "1"]
  }
]

如上示例,在第一次调用search after时指定了sort的值,并且sort中指定了price 倒序排序。为了保证排序的唯一性,我们指定了文档_id作为唯一值

可以看到,第一次调用的返回结果除了文档的信息外,还有sort相关的信息,在下一次调用的时候需要带上最后一个文档的sort值,示例中其值为:[19.9, “1”]。

下面的示例是第二次调用search after 接口进行翻页操作:

# 第二次调用 search after
POST books/_search
{
  "size": 2,
  "query": {
    "match_all": {}
  },
  "search_after":[19.9, "1"], # 设置为上次返回结果中最后一个文档的 sort 值
  "sort": [
    { "price": "desc" },
    { "_id": "asc" }
  ]
}

如上示例,进行翻页操作的时候在search after字段中设置上一次返回结果中最后一个文档的sort值,并且保持sort的内容不变

那为啥search after不会产生深度分页的问题那?其关键就是sort中指定的唯一排序值
image.png
如上图,因为有了唯一的排序值做保证,所以每个分片只需要返回比sort中唯一值大的size个数据即可。例如,上一次的查询返回的最后一个文档的sort为a,那么这一次查询只需要在分片1,2,3中返回size个排序比a大的文档,协调节点汇总这些数据进行排序后返回size个结果给客户端

而from + size的方式因为没有唯一值,所以没法保证每个分片上的排序就是全局的排序,必须把每个分片的from+size个数据汇总到协调节点进行排序处理,导致出现了深分页的问题

因为sort的值是根据上一次请求结果来设置的,所以search after不支持跳转到指定的页数,甚至不能返回前一页,只能一页页往下翻。当我们可以结合缓存中间件,把每页返回的sort值缓存下来,实现往前翻页的功能

scroll Api

当我们相对结果集进行排序的时候,例如做全量数据导出时,可以使用scroll Api。scroll Api会创建数据快照,后续的访问将会基于这个快照来进行,所以无法检索新写入的数据。
scroll API 的使用示例如下:

# 第一次使用 scroll API
POST books/_search?scroll=10m
{
  "query": {
    "match_all": {}
  },
  "sort": { "price": "desc" }, 
  "size": 2
}

# 结果
{
  "_scroll_id" : "FGluY2x1ZGVfY29udGV4dF9......==",
  "hits" : {
    "hits" : [
      {
        "_id" : "6",
        "_source" : {
          "book_id" : "4ee82467",
          "price" : 20.9
        }
      },
      ......
    ]
  }
}

如上示例,在第一次使用scroll Api需要初始化scroll 搜索并且创建快照,使用scroll查询参数指定本次“查询上下文(快照)”的有效时间,本示例中为10分钟。

其返回的结果中除了匹配文档的列表外还有_scroll_id, 我们需要在翻页的请求带上这个_scroll_id:

# 进行翻页
POST /_search/scroll                                                    
{
  "scroll" : "5m",   
  "scroll_id" : "FGluY2x1ZGVfY29udGV4dF9......==" 
}

  • 36
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值