Elasticsearch 分页最多不能超1W记录排序以及解决方案以及scroll和scroll-scan区别

1【理解】

1 请求第20页,假设你有16个分片,则需要在coordinate node 汇总到 shards* (from+size)条记录,即需要 16*(20+10)记录后做一次全局排序,再最终取出 from后的size条结果作为最终的响应。

2 当索引非常非常大(千万或亿),是无法安装 from + size 做深分页的,分页越深则越容易OOM,即便不OOM,也是很消耗CPU和内存资源的。

CPU、内存和IO消耗容易理解,网络带宽问题稍难理解一点。在 query 阶段,每个shards需要返回 1,000,100 条数据给 coordinating node,而 coordinating node 需要接收 10 * 1,000,100 条数据,即使每条数据只有 _doc _id 和 _score,这数据量也很大了,而且,这才一个查询请求,那如果再乘以100呢?

3 为了不合理使用 from + size 造成OOM及最终的集群不稳定,官方在后2.x版本中已增加限定 index.max_result_window:10000作为保护措施 ,即默认 from + size 不能超过1万。当然这个参数可以动态修改,也可以在配置文件配置——但最好不要这么做,除非你知道这意味着什么

4 根据业务字段属性作为游标

数据随时间在变化,数据丢失重复等不可预期的结果 ;不能保证 游标的准确性;

 

curl -XPUT "http://11.12.84.126:9200/_audit_0102/_settings" -d '{

"index": {

"max_result_window": 100000

}

}'

 

OOM

???

 

"reason": {

"type": "query_phase_execution_exception",

"reason": "Result window is too large, from + size must be less than or equal to: [10000] but was [10010]. 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."

 

2【解决方案】于是出现了:

海量导数据第一版:

1 A scrolled search takes a snapshot in time(适时)

2 Normally, the background merge process optimizes the index by merging together smaller segments to create new bigger segments, at which time the smaller segments are deleted(合并小的分段到新的大的分段,同时小的分段被删除)于是更多的file handles 被占用

 

3【应用】

POST /note_/recommend_note/_search?scroll=1m

 

{

 

"size": 2,

 

"query": {

 

"match" : {

 

"operation_tags" : "育儿"

 

}

 

}

 

}

 

返回:

 

"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAF1RhFl9ISGlIYnNhUzlhVDdLd01wOGc4WXcAAAAAABl0kRZyTmhoMmJNVlJzQ1VzOEROVUhHRlZnAAAAAAAZdJIWck5oaDJiTVZSc0NVczhETlVIR0ZWZwAAAAAAGXSTFnJOaGgyYk1WUnNDVXM4RE5VSEdGVmcAAAAAABdUYhZfSEhpSGJzYVM5YVQ3S3dNcDhnOFl3"

 

 

 

第二步:

 

POST /_search/scroll

 

{

 

"scroll" : "1m",

 

"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAF1RhFl9ISGlIYnNhUzlhVDdLd01wOGc4WXcAAAAAABl0kRZyTmhoMmJNVlJzQ1VzOEROVUhHRlZnAAAAAAAZdJIWck5oaDJiTVZSc0NVczhETlVIR0ZWZwAAAAAAGXSTFnJOaGgyYk1WUnNDVXM4RE5VSEdGVmcAAAAAABdUYhZfSEhpSGJzYVM5YVQ3S3dNcDhnOFl3"

 

} 一直执行相同的请求直到返回结果为空

 

 

 

第三步:GET /_nodes/stats/indices/search

 

 

 

"indices": {

 

"search": {

 

"open_contexts": 3,

 

"query_total": 1668363,

 

"query_time_in_millis": 14717409,

 

"query_current": 0,

 

"fetch_total": 1570213,

 

"fetch_time_in_millis": 1323603,

 

"fetch_current": 0,

 

"scroll_total": 62483,

 

"scroll_time_in_millis": 679341,

 

"scroll_current": 3,

 

"suggest_total": 0,

 

"suggest_time_in_millis": 0,

 

"suggest_current": 0

 

}

 

}

 

 

 

第四步清理 单个:

 

DELETE /_search/scroll/DnF1ZXJ5VGhlbkZldGNoBQAAAAAAFpvrFm5mZ1ZidkgwUW9hOUR4VVkwUnQ2SmcAAAAAABl1KhZyTmhoMmJNVlJzQ1VzOEROVUhHRlZnAAAAAAAWm-oWbmZnVmJ2SDBRb2E5RHhVWTBSdDZKZwAAAAAAF1VFFl9ISGlIYnNhUzlhVDdLd01wOGc4WXcAAAAAABdVRhZfSEhpSGJzYVM5YVQ3S3dNcDhnOFl3

 

{

 

"succeeded": true,

 

"num_freed": 5

 

}

 

所有:

 

DELETE /_search/scroll/_all

 

{

 

"succeeded": true,

 

"num_freed": 3

 

}

 

 

 

第二种做法

 

POST ip:port/my_index/my_type/_search?search_type=scan&scroll=1m&size=50

 

{

 

"query": { "match_all": {}}

 

}

 

 

 

 

 

java:

 

第一次查询

 

SearchResponse response1 = client.prepareSearch("_audit_0221").setTypes("_log_0221")

 

.setQuery(boolQueryBuilder)

 

.setSearchType(.setSearchType(SearchType.DEFAULT))

 

.setSize(10).setScroll(TimeValue.timeValueMinutes(5))

 

.addSort("logTime", SortOrder.DESC)

 

.execute().actionGet();//第一次查询

 

for (SearchHit searchHit : response1.getHits().hits()) {

 

biz handle....;

 

}

 

第二次查询

 

 

 

while (response1.getHits().hits().length>0) {

 

for (SearchHit searchHit : response1.getHits().hits()) {

 

System.out.println(searchHit.getSource().toString());

 

}

 

response1 = client.prepareSearchScroll(response1.getScrollId()).setScroll(TimeValue.timeValueMinutes(5))

 

.execute().actionGet();

 

}

 

一次性查询清理现场:

 

ClearScrollRequest request = new ClearScrollRequest();

 

request.addScrollId(scrollId);

 

client.clearScroll(request);

4【评价】

这时如果你的产品经理要求你按照常规的做法去分页,你可以很明确的告诉他,你的系统不支持这么深度的分页,翻的越深,性能也就越差。

 

不过这种深度分页场景在现实中确实存在,有些场景下,我们可以说服产品经理很少有人会翻看很久之前的历史数据,但是有些场景下可能一天都产生几百万。这个时候我们可以根据具体场景具体分析。

参考https://my.oschina.net/u/1787735/blog/3024051

5 【scroll和scroll-scan】区别

1. scroll支持排序,scroll-scan不支持排序,是按照索引顺序返回,可以提高查询效率。

2. scroll-scan第一次查询只支持返回id,没有结果。

总结:

1. es的分页查询不支持深度分页,如果偏要使用要结合具体业务场景进行使用。不能当成关系型数据库中的分页进行使用。

2. 要想提高产品体验和查询效率不能过于依赖技术,要结合需求进行分析以提高体验,因为很多搜索类产品都不支持深度分页。

3. 如果在不涉及排序的情况下尽量使用scroll-scan,它是按照索引顺序返回,提高效率。

PS:elasticSearch各个版本可能都稍有区别,但是原理相同。

评论区
Rick ©2018