ElasticSearch 分页问题分析
Posted 爱上口袋的天空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ElasticSearch 分页问题分析相关的知识,希望对你有一定的参考价值。
1、简介
ElasticSearch
是搜索引擎,从搜索的意义上来说,如果筛选条件或前几页都找不到需要的数据,继续深度分页也不会找到想要的数据。
ElasticSearch
不要做深度分页和随机深度跳页。
2、ES 分页建议
- 增加默认的筛选条件,尽量减少数据量的展示,比如:最近一个月;
- 限制总分页数,比如:淘宝、京东仅显示100页查询结果,百度仅显示76页;
- 修改跳页的展现方式,改为滚动显示,或小范围跳页,比如:谷歌、百度的小范围跳页。
3、ES 三种分页比较
from+size
:适用于浅分页(数据量小于max_result_window
),在增大max_result_window
情况下,也可实现深度分页,但效率低下,可能出现 OOM。scroll
:适用于数据导出,基于生成的历史快照查询,对于数据的变更不会反映到快照上。search_after
:适用于实时请求和高并发场景(深度分页+排序),由于每一页的数据依赖于上一页最后一条数据,所以无法做到随机跳页(滚动显示)。
4、from+size方式
使用 from and size 进行分页。但是es官方并不建议用这种方法进行大数据的查询尤其是深度分页,具体原因我们要知道它是如何查询的。
size为返回的结果个数,默认为10;from表示从第几行开始,不包含在内。
假设我们在有5个主分片的索引中搜索,查询第一页数据,即前10条数据,那么es会从每个分片中生成排序好的结果,取出前10条,然后返回给请求节点,请求节点再将这50条记录再次排序选出前10条。
但是如果请求的是第1000页的数据,即第10001至第10010条数据。那么es会从5个分片中取出各个分片顶端的10010条数据,然后将这总共50050条数据返回给请求节点,请求节点再次对这50050条数据进行一次全局排序,取出第50041至50050条数据,抛弃50040条数据,这就造成了很大的浪费。
在分布式系统中排序的花费会随着分页的深度而成倍增长,如果数据特别大对CPU和内存的消耗会非常巨大甚至会导致OutOfMemory,所以这也是为什么网络搜索引擎不能返回多余1000个结果的原因
案例:
GET test_dev/_search { "query": { "bool": { "filter": [ { "term": { "age": 28 } } ] } }, "size": 10, "from": 20, "sort": [ { "timestamp": { "order": "desc" }, "_id": { "order": "desc" } } ] }
5、scroll分页方式
为了满足深度分页的场景,es 提供了 scroll 的方式进行分页读取。原理上是对某次查询生成一个游标 scroll_id , 后续的查询只需要根据这个游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。
可以把 scroll 理解为关系型数据库里的 cursor,因此,scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发。
可以把 scroll 分为初始化和遍历两步,初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照,在遍历时,从这个快照里取数据,也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果,并且维护这个快照需要占用很多资源。
使用scroll,每次只能获取一页的内容,然后会返回一个scrollid,根据scrollid可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景。但是在真正的使用场景中,第10000条数据已经是很后面的数据了,可以“折衷”一下,不提供跳转页面功能,只能下一页的翻页。
可以看出scroll不适合支持那种实时的和用户交互的前端分页工作,其主要用途用于从ES集群分批拉取大量结果集的情况,一般都是offline的应用场景。 比如需要将非常大的结果集拉取出来,存放到其他系统处理,或者需要做大索引的reindex等等。
案例:
GET test_dev/_search?scroll=5m { "query": { "bool": { "filter": [ { "term": { "age": 28 } } ] } }, "size": 10, "from": 0, "sort": [ { "timestamp": { "order": "desc" }, "_id": { "order": "desc" } } ] }
- scroll=5m表示设置scroll_id保留5分钟可用。
- 使用scroll必须要将from设置为0。
- size决定后面每次调用_search搜索返回的数量
然后我们可以通过数据返回的_scroll_id读取下一页内容,每次请求将会读取下10条数据,直到数据读取完毕或者scroll_id保留时间截止:
GET _search/scroll { "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAJZ9Fnk1d......", "scroll": "5m" }
注意:请求的接口不再使用索引名了,而是 _search/scroll,其中GET和POST方法都可以使用。
croll删除
根据官方文档的说法,scroll的搜索上下文会在scroll的保留时间截止后自动清除,但是我们知道scroll是非常消耗资源的,所以一个建议就是当不需要了scroll数据的时候,尽可能快的把scroll_id显式删除掉。清除指定的scroll_id:
DELETE _search/scroll/DnF1ZXJ5VGhlbkZldGNo.....
清除所有的scroll:
DELETE _search/scroll/_all
6、search_after
上述的 scroll search 的方式,官方的建议并不是用于实时的请求,因为每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上。这种方式往往用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。
searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
它的缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序(注:每个文档具有一个唯一值的字段应该用作排序规范的仲裁器。否则,具有相同排序值的文档的排序顺序将是未定义的。建议的方法是使用字段_id,它肯定包含每个文档的一个唯一值)。
此外还有一个与scorll的不同之处是searchAfter的读取数据的顺序会受索引的更新和删除影响而scroll不会,因为searchAfter读取的并不是不可变的快照,而是依赖于上一页最后一条数据,所以无法跳页请求,用于滚动请求,于scroll类似,不同之处在于它使无状态的。
案例:
POST twitter/_search { "size": 10, "query": { "match" : { "title" : "es" } }, "sort": [ {"date": "asc"}, {"_id": "desc"} ] }
返回出的结果信息 :
{ "took" : 29, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 5, "relation" : "eq" }, "max_score" : null, "hits" : [ { ... }, "sort" : [ ... ] }, { ... }, "sort" : [ 124648691, "624812" ] } ] } }
上面的请求会为每一个文档返回一个包含sort排序值的数组。这些sort排序值可以被用于 search_after 参数里以便抓取下一页的数据。比如,我们可以使用最后的一个文档的sort排序值,将它传递给 search_after 参数:
GET twitter/_search { "size": 10, "query": { "match" : { "title" : "es" } }, "search_after": [124648691, "624812"], "sort": [ {"date": "asc"}, {"_id": "desc"} ] }
注意:当我们使用search_after时,from值必须设置为0或者-1。
search_after缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序。它与滚动API非常相似,但与它不同,search_after参数是无状态的,它始终针对最新版本的搜索器进行解析。因此,排序顺序可能会在步行期间发生变化,具体取决于索引的更新和删除。
以上是关于ElasticSearch 分页问题分析的主要内容,如果未能解决你的问题,请参考以下文章
elasticsearch代码片段,及工具类SearchEsUtil.java
ElasticSearch学习问题记录——Invalid shift value in prefixCoded bytes (is encoded value really an INT?)(代码片段