深度分页存在的问题
https://segmentfault.com/a/1190000019004316?utm_source=tag-newest
在实际应用中,分页是必不可少的,例如,前端页面展示数据给用户往往都是分页进行展示的。
1、ES分页搜索
Elasticsearch分页搜索采用的是from+size。from表示查询结果的起始下标,size表示从起始下标开始返回文档的个数。
示例:
什么是深分页(deep paging)?简单来说,就是搜索的特别深,比如总共有60000条数据,三个primary shard,每个shard上分了20000条数据,每页是10条数据,这个时候,你要搜索到第1000页,实际上要拿到的是10001~10010。
注意这里千万不要理解成每个shard都是返回10条数据。这样理解是错误的!
下面做一下详细的分析:
请求首先可能是打到一个不包含这个index的shard的node上去,这个node就是一个协调节点coordinate node,那么这个coordinate node就会将搜索请求转发到index的三个shard所在的node上去。比如说我们之前说的情况下,要搜索60000条数据中的第1000页,实际上每个shard都要将内部的20000条数据中的第10001~10010条数据,拿出来,不是才10条,是10010条数据。3个shard的每个shard都返回10010条数据给协调节点coordinate node,coordinate node会收到总共30030条数据,然后在这些数据中进行排序,根据_score相关度分数,然后取到10001~10010这10条数据,就是我们要的第1000页的10条数据。
如下图所示:
deep paging问题就是说from + size分页太深,那么每个shard都要返回大量数据给coordinate node协调节点,会消耗大量的带宽,内存,CPU。
深度分页问题之所以存在,是和Elasticsearch搜索内部执行原理分不开的。
如果你想查询第5000-5100数据,查询官网API你很容易就知道,发送如下查询条件就可以做到:
POST auditlog_operation/operlog/_search
{
“from”:5000 //from:定义从哪里开始拿数据
“size”:100 //size:定义一共拿多少条数据
}
查询流程如下:
-
客户端发送请求到某个node节点。
-
此node将请求广播到各分片,各分片各自查询前5100条数据。
-
查询结果返回给node节点,node对结果进行合并整合,取出前5100条数据。
-
返回给客户端。
流程大概如下
相信就算是技术小白也能看出上述深度分页查询的问题,如果你要深度获取1000000到1000100页的数据,性能问题会非常明显的暴露出来:CPU、内存、IO、网络带宽等等,而且Elasticsearch本身就是个Java应用,若并发上去,Elasticsearch会快就会OOM
{ "query": { "match_all": {} }, "from": 9990, "size": 10 } { "query": { "match_all": {} }, "from": 9999, "size": 10 }
我们在获取第9999条到10009条数据的时候,其实每个分片都会拿到10009条数据,然后集合在一起,总共是10009*3=30027条数据,针对30027数据再次做排序处理,最终会获取最后10条数据。
如此一来,搜索得太深,就会造成性能问题,会耗费内存和占用cpu。而且es为了性能,他不支持超过一万条数据以上的分页查询。那么如何解决深度分页带来的性能呢?其实我们应该避免深度分页操作(限制分页页数),比如最多只能提供100页的展示,从第101页开始就没了,毕竟用户也不会搜的那么深,我们平时搜索淘宝或者百度,一般也就看个10来页就顶多了。
查询请求:
POST auditlog_operation/operlog/_search
{
“from”:10000
“size”:100
}
如果你尝试发送上述from+size请求来获取10000-10100条数据,对不起会返回错误:
如果你尝试发送上述from+size请求来获取10000-10100条数据,对不起会返回错误:
{"error":{"root_cause":[{"type":"query_phase_execution_exception","reason":"Resultwindow is too large, from + size must be less than or equal to: [1000000] butwas [1000100]. See the scroll api for a more efficient way to request largedata sets. This limit can be set by changing the [index.max_result_window]index levelparameter."}],"type":"search_phase_execution_exception","reason":"allshards failed","phase":"query_fetch","grouped":true,"failed_shards":[{"shard":0,"index":"auditlog_operation","node":"iqu-KVKjTRmT3YcT9XAu_w","reason":{"type":"query_phase_execution_exception","reason":"Resultwindow is too large, from + size must be less than or equal to: [1000000] butwas [1000100]. See the scroll api for a more efficient way to request largedata sets. This limit can be set by changing the [index.max_result_window]index level parameter."}}]},"status":500}
迅速查询官网得到更明确的提示:
翻译成中文为:注意 from+size不再适用于查询数据超过index.max_result_window设置值,此默认值为10000。查看 Scroll 或 Search After来获取更高效的深层分页(滚动)。
由此可以得到两个结论:
-
你可以修改index.max_result_window设置值来继续使用from+size做分页查询。当然效率肯定不高。
-
如果要找更高效的深度分页方式,请使用Scroll 或者Search After。
Scroll API
Scroll API更适用于检索大量数据(甚至全部数据)。它先做一个初始阶段搜索然后持续批量从Elasticsearch里拉取结果直到返回结果为空。这有点像传统数据库里的cursors(游标)。
https://blog.csdn.net/lisongjia123/article/details/79041402
https://www.sohu.com/a/165387407_465944
https://segmentfault.com/a/1190000019004316?utm_source=tag-newest
除了效率上的问题,还有一个无法解决的问题是,es 目前支持最大的 skip 值是 max_result_window ,默认
为 10000 。也就是当 from + size > max_result_window 时,es 将返回错误
[root@dnsserver ~]# curl -XGET 127.0.0.1:9200/custm/_settings?pretty
{
"custm" : {
"settings" : {
"index" : {
"max_result_window" : "50000",
....
}
}
}
}
最开始的时候是线上客户的es数据出现问题,当分页到几百页的时候,es 无法返回数据,此时为了恢复正常使用,我们采用了紧急规避方案,就是将 max_result_window 的值调至 50000,其中custm是索引的名称,http请求提交的方式必须是put请求
[root@dnsserver ~]# curl -XPUT "127.0.0.1:9200/custm/_settings" -d
'{
"index" : {
"max_result_window" : 50000
}
}'
然后这种方式只能暂时解决问题,当es 的使用越来越多,数据量越来越大,深度分页的场景越来越复杂时,如何解决这种问题呢?
custm