1、引言
Elasticsearch中的 Terms 桶聚合基于我们的数据动态构建桶;但是它并不知道到底生成了多少桶。 大多数时候对单个字段的聚合查询还是非常快的, 但是当需要同时聚合多个字段时,就可能会产生大量的分组,最终结果就是占用 es 大量内存,从而导致 OOM 的情况发生。
在Elasticsearch中,对于具有许多唯一术语和少量所需结果的字段,延迟子聚合的计算直到顶部父级聚合被修剪会更有效。通常,聚合树的所有分支都在一次深度优先传递中展开,然后才会发生任何修剪。在某些情况下,非常浪费资源,并且可能会遇到内存限制。
而本文所讲的内容即通过 DFS 和 BFS 提升检索效率和提升聚合性能,基本原理即:推迟子聚合的计算。
2、案例
假设有索引actor_films
,存储信息为某些演员和其出演过的一些电影。
2.1 数据
PUT /actor_films/_doc/1 { "name": "成龙", "films": [ { "name": "A计划", "collect": 210 }, { "name": "B计划", "collect": 200 }, { "name": "C计划", "collect": 230 }, { "name": "D计划", "collect": 250 } ] } PUT /actor_films/_doc/2 { "name": "李连杰", "films": [ { "name": "功夫", "collect": 310 }, { "name": "少林寺", "collect": 400 }, { "name": "峨眉", "collect": 530 } ] } PUT /actor_films/_doc/3 { "name": "吴京", "films": [ { "name": "战狼", "collect": 210 }, { "name": "战狼2", "collect": 500 }, { "name": "流浪地球", "collect": 630 } ] }
2.2 假设有如下需求:
统计演员列表中总票房最高的前十位演员每个人票房最高的前五部电影
因为无法导入大量数据,并且聚合代码本身非本文所讲解重点,因此下面代码采用伪代码方式,即跳过具体的逻辑部分。
伪代码如下:
GET actor_films/_search { "size": 0, "aggs": { "actors_agg": { "terms": { "field": "name.keyword", "size": 10 // 这里跳过了计算过程,假设默认排序就是票房排序倒序排列 }, "aggs": { "movies_agg": { "terms": { "field": "movies.name.keyword", "size": 5 // 假设默认就是票房排序 } } } } } }
2.3 性能痛点
首先,上述代码描述的问题可用下图表示,假设演员数据有M个,M是一个很大的数值,比如1万、10万或者更多。每位演员出演过N部电影,每个M对应的N可能不同,N 大于5。
按照上述需求,我们最终要返回的桶数量最大值为 50。默认情况下,ES 会先构建完整的树,然后修剪无用节点。下图中表示即先遍历 演员1,然后遍历演员一的第一个分支,直至第一个分支没有子节点,回溯值演员一的第二个分支,直至遍历完演员1 的所有分支,回溯至Entry,然后遍历演员2。最终遍历节点数为 MN。如果演员数量有1万,平均每个演员10部电影,此时遍历所产生的的计算为10万次,而我们真正需要的只有50次!
3 解决方式
3.1 Collect mode
ES 中允许设置参数collect_mode
:
"collect_mode": "{collect_mode.value}"
3.2 参数
breadth_first:即使用广度优先算法。即:先做第一层聚合,逐层修剪。广度优先仅仅适用于每个组的聚合数量远远小于当前总组数的情况下,因为广度优先会在内存中缓存裁剪后的仅仅需要缓存的每个组的所有数据,以便于它的子聚合分组查询可以复用上级聚合的数据。广度优先的内存使用情况与裁剪后的缓存分组数据量是成线性的。对于很多聚合来说,每个桶内的文档数量是相当大的。
depth_first:使用深度优先算法,即:先构建完整的树,然后修剪无用节点。
3.3 完整代码如下
GET actor_films/_search { "size": 0, "aggs": { "actors_agg": { "terms": { "field": "name.keyword", "size": 10, // 这里跳过了计算过程,假设默认排序就是票房排序倒序排列 "collect_mode": "breadth_first" // 使用广度优先搜索 }, "aggs": { "movies_agg": { "terms": { "field": "movies.name.keyword", "size": 5 // 假设默认就是票房排序 } } } } } }