ES 分页方案
ES 中,存在三种常见的分页方案:
- FROM, SIZE
- Search-After
- Scroll
下面将依次比较三种方案之间的 trede-off,并给出相应建议的应用场景。
常见分页,FROM, SIZE
ES 提供了常见的分页功能,通过在 search API 中,指定 from 和 size 来实现分页的效果:
{
"from": 10,
"size": 20,
"sort": [{"timestamp": "desc"}],
"query": {
"match_all": {} # 返回所有 doc
}
}
from: 表示起点位置,默认是 0.
size:表示返回的数量,默认是 10.
这种分页方式到没什么好说的,但需要注意的是由于 ES 为了支持海量数据的查询,本身采用了分布式的架构。
而对于分布式架构来说,存在一个典型的深度分页的问题。
ES 在存储数据时,会将其分配到不同的 shard 中。在查询时,如果 from 值过大,就会导致分页起点太深。
每个 shard 查询时,都会将 from 之前位置的所有数据和请求 size 的总数返回给 coordinator. 简单来说,就是想取第 n 页的内容,但是却返回了前 n 的内容。
而对于 coordinator 来说,会显著导致内存和CPU使用率升高,特别是在高并发的场景下,导致性能下降或者节点故障。
举例来说,当前 ES 共有 4 个 shard,并且每个 shard 没有副本。假如分页的大小为 10. 然后想取第11 页前 5 条内容。对应的 from = 1000,size = 5.
ES 的查询过程为:
- 每个 shard 将所在数据加载到内存并排序,然后取前 1005 个,返回给 coordinator.
- 每个 shard 都执行上面的操作。
- 最后 coordinator 将 1005 * 4 = 4020 条数据排序,然后取 5 条数据返回。
可以发现,from 的位置太深,造成了如下的问题:
- 返回给 coordinator 数值太大,明明就需要 5 条数据,但却给 coordinator 1005 条数据
- coordinator 需要处理每个 shard 返回前 11 页的结果。但需要的仅是第 11 页的内容,却对前 11 页的内容进行了排序,浪费了内存和 cpu 的资源。
ES 为了规避这个问题,通过设置 max_result_window 来限制 from 和 size 的大小,默认大小仅支持 10000 条。当超过 10000 的大小,则会报出异常。
在页数不深或者考虑内存,低并发等情况,可以通过临时调整 max_result_window 来解决该问题,但如果页数太深则建议使用的 Search-After 的方式。
SearchAfter 分页
为了应对深度分页的情况,ES 推荐使用 SearchAfter 的方式,来实现数据的深度翻页检索。
在具体实现上,通过动态指针的技术。在第一次使用 search api 查询时,附带一个 sort 参数,其中 sort 的值必须唯一,可以用 _id
作为排序参数。
{
"from": 0,
"size": 1,
"sort": [
{"timestamp": "desc"},
{"_id": "asc"}
],
"query": {
"match_all": {}
}
}
每个 shard 在排序后会记录当前查询的最后位置,然后将其返回。
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "cmi_alarm_info",
"_type": "_doc",
"_id": "1,3,d_to_s_JitterAvg",
"_score": null,
"_source": {
"src_device_id": 1,
"dst_device_id": 3,
"type": "d_to_s_JitterAvg",
"status": "normal",
"create_time": 1617085800
},
"sort": [
1617085800,
"1,3,d_to_s_JitterAvg"
]
}
]
}
}
下次查询时,在 search_after
携带 Response 中返回的 sort 参数,实现分页的查询。
{
"from": 0,
"size": 1,
"sort": [
{"timestamp": "desc"},
{"_id": "asc"}
],
"query": {
"match_all": {}
},
"search_after": [
1617084900,
"13,3,d_to_s_JitterAvg"
]
}
和 from size 的查询方式相比, 每个 shard 每次返回给 coordinator 的结果仅为 size 数量,将空间复杂度由 O(n) 降为 O(1).
但 Search-after 也有一些问题:
首先就是不支持跳页的情况。
如果需求上一定需要跳页时,只能通过 from 或者 size 的方式。同时为了避免深度分页的问题,一般可以采用限制页面数量的方式。在确定 size 后,设置一个最大的分页值。在查询时,分页数不允许超过该值。
其次,随着翻页深度的增加,查询的效率也会有所降低,但不会导致 OOM,算是可以完成深度查询的任务。原因在于,虽然说通过排序字段,可以很好的定位出下一次翻页的开始位置。但在每次请求时,从头扫描该字段,找到该字段的位置。页数越深,找到该位置的时间也就越长。
Scroll 分页
虽然说 search-after 可以在一定程度上避免深度分页的问题,但在处理大数据量,效率并不高。在一些对实时性要求不高的场景,如利用 Spark 进行大规模计算时。就可以利用 scroll 分页的方式,检索所有数据。
scroll 的请求方式分为两步:
- 第一次请求,ES 会返回生成生成的 scroll_id
- 之后的请求,不断使用 scroll_id 进行查询,直到所有数据被检索完成。
第一次请求,添加 scroll 标识,并拿到 scroll_id 作为下次请求的参数:
POST /my-index-000001/_search?scroll=1m
{
"size": 100,
"query": {
"match": {
"message": "foo"
}
}
}
Response:
{
"_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAADlx8Wb0VzanNRSENRbUtBQVEzbHltcF9WQQ==",
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
}
}
第二次请求,使用 scroll_id 直到遍历完所有数据:
POST /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
对于 Scroll 来说,会返回第一次请求时刻的所有文档,之后文档的改变并不会被查询到,保留的时间通过 scroll 参数指定。在查询性能上,时间和空间复杂度都为 O(1),能以恒定的速度查询完所有数据。
在原理上,相当于第一次查询阶段, 保留所有的 doc id 信息。在随后的查询中,根据的需要的 doc id,在不同的 shard 中拉取不同的文档。和 search-after 相比,省去了每次都要全局排序的过程。
总结
from, size 适用于常见的查询,例如需要支持跳页并实时查询的场景。但查询深度过深时,会有深度分页的问题,造成 OOM.
如果在业务上,可以不选择跳页的方式,可以使用的 search-after 的方式避免深度分页的问题。但如果一定要跳页的话,只能采用限制最大分页数的方式。
但对于超大数据量,以及需要高并发获取等离线场景,scroll 是比较好的一种方式。
参考
ES 分页方案的更多相关文章
- es分页搜索
1.es分页语法GET /_search?from=起始数&size=页面显示条数例如:GET /test_index/test_type/_search?from=0&size=3 ...
- agumaster 分页方案
本文例程下载:https://files.cnblogs.com/files/xiandedanteng/agumaster20200430-1.zip 之前的分页方案有点小瑕疵,这回修正了一下. 控 ...
- mysql高效分页方案及原理
很久以前的一次面试中,被面试官问到这个问题,由于平时用到的分页方法不多,只从索引.分表.使用子查询精准定位偏移以外,没有使用到其它方法. 后来在看其它博客看到了一些不同的方案,也一直没有整理.今天有时 ...
- es分页条数限制
"error": { "root_cause": [ { "type": "query_phase_execution_excep ...
- Vue Element Tabe Pager 分页方案
表格和分页分离的,但是使用中,却是结合在一起的. 分析 有以下方式触发查询: mounted 加载数据. 查询按钮 加载数据. pager 变化加载数据 加载数据函数: loadData 问题 mou ...
- Mysql 千万级快速查询|分页方案
1.简单的 直接查主键id SELECT id FROM tblist WHERE LIMIT 500000,10 2对于有where 条件,又想走索引用limit的,必须创建一个索引,将where ...
- SQL分页语句三方案
方法一: SELECT TOP 页大小 * FROM table1 WHERE id NOT IN ( SELECT TOP 页大小*(页数-1) id FROM table1 ORDER BY id ...
- 分页查询es时,返回的数据不是自己所期望的问题
在进行es分页查询时,一般都是用sql语句转成es查询字符串,在项目中遇到过不少次返回的数据不是自己所期望的那样时,多半原因是自己的sql拼接的有问题. 解决办法:务必要保证自己的sql语句拼接正确.
- 日均5亿查询量的京东订单中心,为什么舍MySQL用ES?
阅读本文大概需要 8 分钟. 来源:京东技术订阅号(ID:jingdongjishu) 作者:张sir 京东到家订单中心系统业务中,无论是外部商家的订单生产,或是内部上下游系统的依赖,订单查询的调 ...
随机推荐
- HTTP cache in depth
HTTP cache in depth HTTP 缓存 https://developers.google.com/web/fundamentals/performance/optimizing-co ...
- how to copy to clipboard using windows cmd
how to copy to clipboard using windows cmd Windows clipboard command line https://www.labnol.org/sof ...
- CSS3 & gradient & color & background
CSS3 & gradient & color & background css background https://developer.mozilla.org/en-US/ ...
- 比特币大涨之际,VAST空投火爆来袭!
昨天特斯拉宣布投资了15亿美元的比特币,消息一出,币圈沸腾,比特币瞬间拉升,突破4万美元关口,现价46000美元!大涨20%,又刷新历史新高! 另外NGK项目热度最高的BGV也是不断刷新历史新高,数据 ...
- JUC并发集合类CopyOnWriteList
CopyOnWriteList简介 ArrayList是线程不安全的,于是JDK新增加了一个线程并发安全的List--CopyOnWriteList,中心思想就是copy-on-write,简单来说是 ...
- git笔记整理-learnGitBranching
声明 此篇文章内容是本人在 github上寻找到Peter Cottle的项目 https://github.com/pcottle/learnGitBranching.git 中学习git相关命令时 ...
- Angular性能优化实践——巧用第三方组件和懒加载技术
应该有很多人都抱怨过 Angular 应用的性能问题.其实,在搭建Angular项目时,通过使用打包.懒加载.变化检测策略和缓存技术,再辅助第三方组件,便可有效提升项目性能. 为了帮助开发者深入理解和 ...
- Unity3d 拖拽脚本报错Can't add the script component "" because the script class cannot be found
解决办法: ①报错原因:文件名与文件内容中的类名不相符. ②关闭360.鲁大师等防护软件,重新安装系统.
- 一次"内存泄漏"引发的血案
本文转载自一次"内存泄漏"引发的血案 导语 2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写.重编发布一段时间后, ...
- RabbitMq手动确认时的重试机制
本文转载自RabbitMq手动确认时的重试机制 消息手动确认模式的几点说明 监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败 如果不手动确认,也不抛出异常,消息不会自动重新推送 ...