ElasticSearch分页与深度分页问题解决
ElasticSearch分页
POST movies/_search
{
"from": 10000,
"size": 1,
"query": {
"match_all": {
}
}
}
这是ElasticSearch最简单的分页查询,但以上命令是会报错的。
报错信息,指window默认是10000。
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
}
],
"type": "search_phase_execution_exception",
怎么解决这个问题,首先能想到的就是调大这个window。
PUT movies/_settings
{
"index" : {
"max_result_window" : 20000
}
}
但这种方法只是暂时解决问题,当数据量越来越大,分页也越来越深,还是会出问题的。
ElasticSearch在分布式系统中的深度分页
理解为什么深度分页是有问题的,我们可以假设在一个有 4 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 40 个结果排序得到全部结果的前 10 个。
现在假设我们请求第 990 页--结果从 990 到 1000 。所有都以相同的方式工作除了每个分片不得不产生前1000个结果以外。 然后协调节点对全部 4000 个结果排序最后丢弃掉这些结果中的 3990 个结果。
可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 10000 个结果的原因。
ElasticSearch深度分页解决方法
Search After
实时获取下一页文档信息
- 不支持指定页数
- 只能往下翻
假设Size 10,当查询990-1000,它通过唯一排序值定位,将每次要处理的文档数都控制在10。
如何使用?举个例子
-
插入示例数据
POST users/_bulk { "index" : { } } { "name":"user10","age":10} { "index" : { } } { "name":"user11","age":11} { "index" : { } } { "name":"user12","age":12} { "index" : { } } { "name":"user13","age":13}
-
查询第一页
POST users/_search { "size": 1, "query": { "match_all": {} }, "sort": [ {"age": "desc"} , {"_id": "asc"} ] }
这里返回数据
{ "took" : 0, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 5, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "users", "_type" : "_doc", "_id" : "sP8mOG0BISxFcLcZlTXF", "_score" : null, "_source" : { "name" : "user13", "age" : 13 }, "sort" : [ 13, "sP8mOG0BISxFcLcZlTXF" ] } ] } }
-
拿到上一页sort信息,放入search_after,往下翻
POST users/_search { "size": 1, "query": { "match_all": {} }, "search_after": [ 13, "sP8mOG0BISxFcLcZlTXF"], "sort": [ {"age": "desc"} , {"_id": "asc"} ] }
Scroll
创建一个快照,所以当新的数据写入时,无法被查询到。
需要每次查询后,输入上一次的Scroll id。
如何使用?举个例子
- 插入示例数据
可以使用search-after例子上的数据
-
查询第一页
POST /users/_search?scroll=5m { "size": 1, "query": { "match_all" : { } } }
-
拿到scroll_id后,往下翻。不同于search_after,接下去第三页、第四页继续使用这个id即可。
POST /_search/scroll { "scroll" : "5m", "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAWAWbWdoQXR2d3ZUd2kzSThwVTh4bVE0QQ==" }
-
查询当前有多少scroll,并删除
为了防止因打开太多卷轴而导致的问题,不允许用户打开滚动超过某个限制。默认情况下,打开的滚动的最大数量为500.可以使用search.max_open_scroll_context群集设置更新此限制。
获取有多少个scroll
GET /_nodes/stats/indices/search
删除所有的scroll或者根据scroll_id删除
DELETE /_search/scroll { "scroll_id" : [ "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==", "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB" ] }
DELETE /_search/scroll { "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" }
DELETE /_search/scroll/_all
总结
- 传统方式(from&size)
需要实时获取顶部的部分文档。例如查询最新的订单。
- Scroll
需要全部文档,例如导出全部数据
- Search After
需要做到深度分页
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。