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

05. 嵌套查询建议使用 Composite 聚合查询方式 。对于常见的 Group by A,B,C 这种多维度 Groupby 查询,嵌套聚合的性能很差,嵌套聚合被设计为在每个桶内进行指标计算,对于平铺的 Group by 来说有存在很多冗余计算,另外在 Meta 字段上的序列化反序列化代价也非常大,这类 Group by 替换为 Composite 可以将查询速度提升 2 倍左右 。
正例:
// 创建Composite Aggregation构建器CompositeAggregationBuilder compositeAggregationBuilder = AggregationBuilders.composite("group_by_A_B_C").sources(AggregationBuilders.terms("group_by_A").field("fieldA.keyword"),AggregationBuilders.terms("group_by_B").field("fieldB.keyword"),AggregationBuilders.terms("group_by_C").field("fieldC.keyword"));// 创建查询条件SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).aggregation(compositeAggregationBuilder).size(0);反例:
// 创建Terms Aggregation构建器,按照字段A分组TermsAggregationBuilder termsAggregationA = AggregationBuilders.terms("group_by_A").field("fieldA.keyword");// 在字段A的基础上创建Terms Aggregation构建器,按照字段B分组TermsAggregationBuilder termsAggregationB = AggregationBuilders.terms("group_by_B").field("fieldB.keyword");// 在字段B的基础上创建Terms Aggregation构建器 , 按照字段C分组TermsAggregationBuilder termsAggregationC = AggregationBuilders.terms("group_by_C").field("fieldC.keyword");// 将字段C的聚合添加到字段B的聚合中termsAggregationB.subAggregation(termsAggregationC);// 将字段B的聚合添加到字段A的聚合中termsAggregationA.subAggregation(termsAggregationB);// 创建查询条件SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).aggregation(termsAggregationA).size(0);06. 避免大聚合查询 。聚合查询的中间结果和最终结果都会在内存中进行,数据量太大会导致内存耗尽 。
07. 高基数场景嵌套聚合查询建议使用 BFS 搜索 。聚合是在 ES 内存完成的 。当一个聚合操作包含了嵌套的聚合操作时,每个嵌套的聚合操作都会使用上一级聚合操作中构建出的桶作为输入 , 然后根据自己的聚合条件再进行桶的进一步分组 。这样对于每一层嵌套,都会再次动态构建一组新的聚合桶 。在高基数场景 , 嵌套聚合操作会导致聚合桶数量随着嵌套层数的增加指数级增长,最终结果就是占用 ES 大量内存,从而导致 OOM 的情况发生 。
默认情况下,ES 使用 DFS(深度优先)搜索 。深度优先先构建完整的树,然后修剪无用节点 。BFS(广度优先)先执行第一层聚合,再继续下一层聚合之前会先做修剪 。
在聚合查询中,使用广度优先算法需要在每个桶级别上缓存文档数据,然后在剪枝阶段后向子聚合重放这些文档 。因此,广度优先算法的内存消耗取决于每个桶中的文档数量 。对于许多聚合查询,每个桶中的文档数量都非常大 , 聚合可能会有数千或数十万个文档 。
但是,有大量桶但每个桶中文档数量相对较少的情况下,使用广度优先算法能更加高效地利用内存资源,而且可以让我们构建更加复杂的聚合查询 。虽然可能会产生大量的桶,但每个桶中只有相对较少的文档,因此使用广度优先搜索算法可以更加节约内存 。
参考示例:
searchSourceBuilder.aggregation(AggregationBuilders.terms("brandIds").collectMode(Aggregator.SubAggCollectionMode.BREADTH_FIRST).field("brandId").size(2000).order(BucketOrder.key(true)));08.避免对 text 字段类型使用聚合查询 。

  • text 的 Fielddata 会加大对内存的占用 , 如有需求使用,建议使用 Keyword 。
09. 不建议使用 bucket_sort 进行聚合深分页查询 。ES 的高 Cardinality 聚合查询非常消耗内存 , 超过百万基数的聚合很容易导致节点内存不够用以至 OOM 。
bucket_sort 使用桶排序算法,性能问题主要是由于它需要在内存中缓存所有的文档和聚合桶,然后才能进行排序和分页,随着文档数量增多和分页深度增加,性能会逐渐变差,有深分页问题 。因为桶排序需要对所有文档进行整体排序,所以它的时间复杂度是 O(NlogN),其中 N 是文档总数 。
目前Elasticsearch支持聚合分页(滚动聚合)的目前只有复合聚合(Composite Aggregation)一种 。滚动的方式类似于SearchAfter 。聚合时指定一个复合键 , 然后每个分片都按照这个复合键进行排序和聚合,不需要在内存中缓存所有文档和桶,而是可以每次返回一页的数据 。


推荐阅读