ES深分页问题分析与解决方案

深度分页问题

什么是深度分页

当我们使用分页查询时,from+size的值超过了10000就会报错

在这里插入图片描述

ES通过参数index.max_result_window用来限制单次查询满足查询条件的结果窗口的大小,其默认值为10000。一般不建议修改。



深度分页会带来什么问题

ES分页查询流程大致如下:

  1. 数据存储在各个分片中,协调节点将查询请求转发给各个节点,当各个节点执行搜索后,将排序后的前N条数据返回给协调节点。
  2. 协调节点汇总各个分片返回的数据,再次排序,最终返回前N条数据给客户端。
  3. 这个流程会导致一个深度分页的问题,也就是翻页越多,性能越差,甚至导致ES出现OOM。

在分布式系统中,对结果排序的成本随分页的深度成指数上升。



案例 从10万名高考生中查询成绩为的10001-10100位的100名考生的信息。

10条数据,集群shard数为5,每个shard保存2W数据,那么这一次查询每个节点都会返回10100条数据,所以最终就会有50500条数据到协调节点中。

各个shard先进行一次排序,在进行二次排序,而二次排序是在堆heap中进行的。

单次查询的数据越大,heap中汇总的数据也就越多,这里单次查询指的是第几条数据开始查询,而不是查询多少条数据。

如果查询的数据排序越靠后,就越容易导致OOM(Out Of Memory)情况的发生,频繁的深分页查询会导致频繁的FGC。

ES为了避免用户在不了解其内部原理的情况下而做出错误的操作,设置了一个阈值,即max_result_window,其默认值为10000,其作用是为了保护堆内存不被错误操作导致溢出。

在这里插入图片描述



深度分页问题的常见解决方案

避免使用深度分页

业务上避免使用深度分页,比如删除"跳页"的功能

比如常见是百度、谷歌搜索引擎

在这里插入图片描述



滚动查询Scroll Search

scroll滚动搜索是先搜索一批数据,然后下次再搜索下一批数据,以此类推,直到搜索出全部的数据来。

scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该视图快照搜索数据,如果在搜索期间数据发生了变更,用户是看不到变更的数据的。因此,滚动查询不适合实时性要求高的搜索场景。

官方已不推荐使用滚动查询进行深度分页查询,因为无法保存索引状态。



适合场景

单个滚动搜索请求中检索大量结果,即非“C端业务”场景



使用

1)第一次进行scroll查询:

#查询命令中新增scroll=1m,说明采用游标查询,保持游标查询窗口1分钟,也就是本次快照的结果缓存起来的有效时间是1分钟。
GET /sys_user/_search?scroll=1m
{
  "query": {"match_all": {}},
  "size": 2
}

查询结果:除了返回前2条记录,还返回了一个游标ID值_scroll_id。

在这里插入图片描述



2)从第二次查询开始,每次查询都要指定_scroll_id参数:

# scroll_id 的值就是上一个请求中返回的 _scroll_id 的值
GET /_search/scroll
{
    "scroll": "1m", 
    "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}



查询结果:

在这里插入图片描述

在这一次快照查询中,scroll_id使用的是同一个,也就是每次请求返回的都是同一个

多次根据scroll_id游标查询,直到没有数据返回则结束查询。采用游标查询索引全量数据,更安全高效,限制了单次对内存的消耗。



删除游标scroll

scroll超过超时后,搜索上下文会自动删除。然而,保持scroll打开是有代价的,因此一旦不再使用,就应明确清除scroll上下文

DELETE /_search/scroll
{
  "scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFl9iZGswNVpPVDEyODJWV0Y5RmJyOWcAAAAAAABLuxZMQzVnanZTeVRoS1plYUdoNmlzUldn"
}



注意事项

  • scroll滚动查询不适合实时性要求高的查询场景,比较适合数据迁移的场景。
  • scroll查询完毕后,要手动清理掉 scroll_id 。虽然ES有自动清理机制,但是 srcoll_id 的存在会耗费大量的资源来保存一份当前查询结果集映像,并且会占用文件描述符。

官方建议:ES7之后,不再建议使用scroll API进行深度分页。如果要分页检索超过 Top 10,000+ 结果时,推荐使用:PIT + search_after。



search_after

参考文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#search-after

scroll API适用于高效的深度滚动,但滚动上下文成本高昂,不建议将其用于实时用户请求。而search_after参数通过提供一个活动光标来规避这个问题。这样可以使用上一页的结果来帮助检索下一页。

search_after 分页查询可以简单概括为如下几个步骤:

1)获取索引的pit

使用 search_after 需要具有相同查询和排序值的多个搜索请求。 如果在这些请求之间发生刷新,结果的顺序可能会发生变化,从而导致跨页面的结果不一致。 为防止出现这种情况,可以创建一个时间点 (PIT) 以保留搜索中的当前索引状态。Point In Time(PIT)是 Elasticsearch 7.10 版本之后才有的新特性。

# 创建一个时间点(PIT)来保存搜索期间的当前索引状态
POST /es_db/_pit?keep_alive=1m
#返回结果,会返回一个PID的值
{
  "id" : "39K1AwEIc3lzX3VzZXIWZXdNVGN4Y1lTSlcxZU9PbGFDcmlWZwAWTEM1Z2p2U3lUaEtaZWFHaDZpc1JXZwAAAAAAAABOJhZfYmRrMDVaT1QxMjgyVldGOUZicjlnAAEWZXdNVGN4Y1lTSlcxZU9PbGFDcmlWZwAA"
}



2) 根据pit首次查询

GET /_search
{
  "query": {
    "match_all": {}
  },
  "pit": {
    "id" : "39K1AwEIc3lzX3VzZXIWZXdNVGN4Y1lTSlcxZU9PbGFDcmlWZwAWTEM1Z2p2U3lUaEtaZWFHaDZpc1JXZwAAAAAAAABO3RZfYmRrMDVaT1QxMjgyVldGOUZicjlnAAEWZXdNVGN4Y1lTSlcxZU9PbGFDcmlWZwAA",
    "keep_alive": "1m"
  },
  "size": 2,
  "sort": [
    {
      "_id": {
        "order": "asc"
      }
    }
  ]
}

在这里插入图片描述



3)根据search_after和pit进行翻页查询

要获得下一页结果,请使用最后一次命中的sort排序值作为 search_after 参数重新运行先前的搜索。

如果使用 PIT,请在 pit.id 参数中使用最新的 PIT ID。 搜索的查询和排序参数必须保持不变。

# 使用上一次请求 返回的pid
# 在 search_after 填写上一次请求最后返回的sort值
GET /_search
{
  "query": {
    "match_all": {}
  },
  "pit": {
    "id" : "39K1AwEIc3lzX3VzZXIWZXdNVGN4Y1lTSlcxZU9PbGFDcmlWZwAWTEM1Z2p2U3lUaEtaZWFHaDZpc1JXZwAAAAAAAABQdhZfYmRrMDVaT1QxMjgyVldGOUZicjlnAAEWZXdNVGN4Y1lTSlcxZU9PbGFDcmlWZwAA",
    "keep_alive": "5m"
  },
  "size": 2,
  "sort": [
    {
      "_id": {
        "order": "asc"
      }
    }
  ],
  "search_after": [
    2
  ]
}

在这里插入图片描述



总结

分页方式性能优点缺点适用场景
from + size灵活性好,实现简单,支持随机翻页受制于max_result_window设置,不能无限制翻页;
存在深度翻译问题,越往后翻译越慢。
数据量比较小,能容忍深度分页问题
scroll解决了深度分页问题scroll查询的相应数据是非实时的,如果遍历过程中插入新的数据,是查询不到的;
保留上下文需要足够的堆内存空间。
海量数据的导出,需要查询海量结果集的数据
search_after性能最好,不存在深度分页问题,能够反映数据的实时变更实现复杂,需要有一个全局唯一的字段连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果,它不适用于大幅度跳页查询海量数据的分页
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值