场景
做分页查询,当分页达到一定量的时候,报如下错误:
Resultwindowis too large, from+ size must be less than or equal to: [10000] but was [78020]. See the scroll api for a more efficient way to request large data sets. This limit can be setby changing the [index.max_result_window] index level setting.
原因分析: es对from + size的大小进行限制,必须小于等于10000。
解决方案:
方案一(有风险)
将max_result_window参数阈值调大,在业务中限制分页大小,使from+size<=10000;
具体操作
改法一:
动态更改索引设置,为max_result_window参数赋值足够大的值,此处改法有两种,具体如下:
基于特定索引生效:
PUT <index_name>/_settings
{
"index.max_result_window": <number>
}
基于全局生效配置:
PUT _all/_settings
{
"index.max_result_window": <number>
}
改法二:
修改配置文件elasticsearch.yml,增加下列配置,并重启elasticsearch。
index.max_result_window: 100000000
其中改法一只当前有效,elasticsearch重启失效,改法二重启后生效。
PS:这种改法的确能解决眼前问题(网上教程普遍做法),但是会带来严重的后果,得结合业务去考虑,如果你的业务索引库存储数据量大,即单个文档字段多,那么随意改动最大结果窗口配置最常见的就是后期会有OOM现象,而且很难发现原因。
问题分析
![](https://i-blog.csdnimg.cn/blog_migrate/77bb524d04fce921ba85eb2deefaa354.png)
正常情况下ES的分页代码如实下面这样的
GET order_2290w/_search
{
"from": 0,
"size": 5
}
返回结果
![](https://i-blog.csdnimg.cn/blog_migrate/e40c394da9ffab1b84df9fa9f8bee5a9.png)
看查询结果我们可以得知,这是一个查询结果集前5条数据的一个检索,即查询第一页的5条数据。返回结果图中数字2即返回的五条文档数据。
如何触发我们标题中的错误,自然是要查询足够大分页的数据啦,达到什么程度呢?当from + size大于10000的时候,就会出现问题,如下图报错信息所示:
![](https://i-blog.csdnimg.cn/blog_migrate/0fb7d423aca16c589fd1644ea0598190.png)
报错信息的解释为当前查询的结果超过了10000的最大值。那么疑问就来了,明明只查询了5条数据,为什么它计算最大值要加上我from的数量呢?这里有一个字很关键:“前”,前10000条意味着什么?
ES的from+size查询机制
假设我们的ES有三个节点,当分页查询请求过来时,如果落到node1节点,那么node1节点将会向node2和node3发送同样的查询请求,每个节点将topN的文档返回(这里只返回文档的id以及打分排序的字段,减少数据传输),node1会对三个节点的所有文档(3N个)进行排序,取topN后再根据文档的id到对应的节点上查询整个文档数据,最后返回客户端。
而对于分页查询,比如from=10000,szie=10000,其实每个节点需要查询from+size=20000条数据,排序之后截取后10000条数据。当我们进行深度分页,比如查询第十页数据时,每个节点需要查询10size=10W条数据,这个太恐怖了。而且默认情况下,当from+size大于10000时,查询会抛出一个异常,ES2.0后有一个max_result_window属性的设置,默认值是10000,也就是from+size的最大限度。当然你可以修改这个值作为临时的应对策略,不过治标不治本,产品也只会变本加厉!
max_result_window参数的具体含义
max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析各项指标从而确定一个最佳值,并非越大约好。
方案二:官方推荐的解决方案 search_after查询
search_after是ES5.0及之后版本提供的新特性,search_after有点类似scroll,但是和scroll又不一样,它提供一个活动的游标,通过上一次查询最后一条数据来进行下一次查询。
比如第一次查询如下:
GET zm/recall/_search
{
{
"query": {
"match_all": {}
},
"sort": [
{
"lastModifyTime": {
"order": "desc"
}
}
],
"size": 10
}
}
这里根据更新时间进行排序,拿到的结果如下:
{"_index":"zmrecall","_type":"recall","_id":"60310505115909","_score":null,"_source":{"userId":60310505115909,"score":1,"city":[276],"sex":1,"age":29,"lastModifyTime":1545037514},"sort":[1545037514]}
注意到返回结果中有一个sort字段,所以下一次查询的时候,只需要将本次查询最后一条数据中的排序字段加入查询即可:
GET zm/recall/_search
{
{
"query": {
"match_all": {}
},
"sort": [
{
"lastModifyTime": {
"order": "desc"
}
}
],
"search_after": [
1545037514
], //这个值与上次查询最后一条数据的sort值一致,支持多个
"size": 10
}
}
如何使用 search after 解决大型搜索引擎场景下深度分页问题
不支持向前搜索,只能向后执行
每次只能向后搜索1页数据
以百度为例,默认加载10页数据,假设每页数据为 10条,其实对于ES而言,还不涉及深度分页问题,因为只是查询了一百条数据。
如果项目并发请求并不高,可以单次查询 100 条数据。或者采用懒加载的方式异步请求前十页数据。后面的数据采用 search after 向后翻页即可。
方案三:scroll查询
ES支持scroll滚屏查询,有兴趣的同学可以了解一下,网上相关的文档不少。不过根据ES官网的描述,scroll查询是很耗性能的方式,不建议在实时查询中运用。官方已不推荐使用滚动查询进行深度分页查询,因为无法保存索引状态。
适合场景
单个滚动搜索请求中检索大量结果。
为了使用滚动,初始搜索请求应该scroll在查询字符串中指定参数,该 参数告诉 Elasticsearch 应该保持“搜索上下文”多长时间,例如?scroll=1m。结果如下:
{
"_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABVWsWN3Q4dDJjcVVRQ0NBbllGMmFqN0ZVZw==",
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 21921750,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
...
]
}
}
BASH 复制 全屏
上述请求的结果包含一个_scroll_id,应将其传递给scrollAPI 以检索下一批结果。
滚动返回在初始搜索请求时与搜索匹配的所有文档。它会忽略对这些文档的任何后续更改。该scroll_id标识一个搜索上下文它记录身边的一切Elasticsearch需要返回正确的文件。搜索上下文由初始请求创建,并由后续请求保持活动状态
GET <index>/_search?scroll=1m
{
"size": 100
}