一口气看完 43 个关于 ElasticSearch 的使用建议( 三 )


反例:使用 bucket_sort 深分页 RT 达到 5000ms+
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();boolQuery.filter(QueryBuilders.termQuery(EsNewApplyDocumentFields.IS_DEL, 0));TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("spuIdAgg").field("spuId").order(BucketOrder.key(false)).size(pageNum*pageSize);termsAggregationBuilder.subAggregation(new BucketSortPipelineAggregationBuilder("spuBucket",null).from((pageNum-1)*pageSize).size(pageSize));searchSourceBuilder.query(boolQuery).aggregation(termsAggregationBuilder).size(0);正例:使用 Composite Aggregation 优化后深分页查询:423ms
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();boolQuery.filter(QueryBuilders.termQuery(EsNewApplyDocumentFields.IS_DEL, 0));CompositeAggregationBuilder compositeBuilder = new CompositeAggregationBuilder("spuIdAgg",Collections.singletonList(new TermsValuesSourceBuilder("spuId").field("spuId").order("desc"))).aggregateAfter(ImmutableMap.of("spuId", "603030")).size(20);searchSourceBuilder.query(boolQuery).aggregation(compositeBuilder).aggregation(totalAgg).size(0);分页10. 避免使用 from+size 方式 。ES 中深度翻页排序的花费会随着分页的深度而成倍增长,分页搜索不会单独“Cache” 。每次分页的请求都是一次重新搜索的过程,而不是从第一次搜索的结果中获取 。如果数据特别大对 CPU 和内存的消耗会非常巨大甚至会导致 OOM 。
11. 避免高实时性&大结果集场景使用 Scroll 方式 。基于快照的上下文 。实时性高的业务场景不建议使用 。大结果集场景将生成大量Scroll 上下文 , 可能导致内存消耗过大,建议使用 SearcheAfter 方式 。
思考:对于 Scroll 和 SearchAfter 的选用怎么看?两者分别适用于哪种场景?SearchAfter 可以完全替代 Scroll 吗?
Scroll 维护一份当前索引段的快照,适用于非实时滚动遍历全量数据查询,但大量Contexts 占用堆内存的代价较高;7.10 引入的新特性 Search After + PIT,查询本质是利用前向页面的一组排序之检索匹配下一页,从而保证数据一致性;8.10 官方文档明确指出不再建议使用 Scroll API 进行深分页 。如果分页检索超过 Top10000+ 推荐使用 PIT + Search After 。
12. SearchAfter 分页/Scroll ID/ 遍历索引中的数据指定 Sort 字段要保证唯一性 , 否则会造成分页/遍历数据不完整或重复 。13. 建议指定业务字段排序 , 不要采用默认打分排序 。ES 默认使用“_score”字段按评分排序 。如在使用 Scroll API 获取数据时,如果没有特殊的排序需求 , 推荐使用"sort":"_doc"让 ES 按索引顺序返回命中文档,可以节省排序开销 。原因如下:

  • 使用非文档 ID 排序,会导致每次查询 ES 需要在每个分片记住上次返回的最后一个文档 , 然后下次查询中会对之前已经返回的文档进行忽略过滤,同时在协调节点进行排序操作 。文档 ID 排序则不需要上述操作 。
  • 对于文档 ID 排序,ES 内部进行了特殊优化,性能表现更优 。
14. Scroll 查询确保显式调用 clearScroll() 方法清除 Scroll ID 。否则会导致 ES 在过期时间前无法释放 Scroll 结果集占用的内存资源 , 同时也会占用默认 3000 个 Scroll 查询的容量,导致 too many scroll ID 的查询拒绝报错,影响业务 。
其他15. 注意 Must 和 Should 同时出现在语句里的时候 , Should 会失效;注意 Must 和 Should 同时出现在同一层级的 bool 查询时,Should 查询会失效 。正例:
{"query":{ "bool":{"must":[{"bool":{"must":[{"term":{"status.keyword":"1"} }]}},{"bool":{"should":[{"term":{"tag.keyword":"1"} } ] }}]}}}反例:
{"query":{"bool":{"must":[{"term":{"status.keyword":"1"}}],"should":[{"term":{"tag.keyword":"1"}}]}}} 16. 避免查询 indexName-* 。因为 Elasticsearch 中的索引名称是全局可见的,可以通过查询所有索引的方式来枚举某个集群中的所有索引名称 。可以通过在 Elasticsearch 配置文件中设置 action.destructive_requires_name 参数来禁止查询 indexName-* 。
17. 脚本使用 Stored 方式,避免使用 Inline 方式 。对于固定结构的 Script,使用 Stored 方式,把脚本通过 Kibana 存入 ES 集群 , 降低重复编译脚本带来的性能损耗 。


推荐阅读