ES terms 聚合功能理解
本文介绍 ES(ES7.8.0) 里面两种不同的聚合统计,cardinality aggregations 和 terms aggregations。为了方便理解,以 MySQL 表的示例数据来讲解 ES 的这两个聚合功能。MySQL 表结构如下:
CREATE TABLE `es_agg_test` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称',
`label` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '标签',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='es agg 测试示例'
示例数据如下:第一列是主键id,第二列是 name,第三列是 label
1,apple,iphone12
2,apple,iphone11
3,apple,iphone11
4,huawei,mate30
5,huawei,mate30
6,huawei,mate30
7,huawei,p30
8,huawei,mate20
一、cardinality 聚合
1、计算 es_agg_test 表中一共有多少个不同的 label?
SQL 写法:
//SQL,输出 5
select count(distinct (label)) from es_agg_test;
ES 代码:
// ES 代码
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.fetchSource(true);
distinct = AggregationBuilders.cardinality("labels").field(label).precisionThreshold(10000);
sourceBuilder.aggregation(distinct);
SearchRequest searchRequest = buildSearchRequest(INDEX, sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
protected SearchRequest buildSearchRequest(String index, SearchSourceBuilder sourceBuilder) {
SearchRequest request = new SearchRequest(index);
request.searchType(SearchType.DEFAULT);
request.source(sourceBuilder);
return request;
}
cardinality 聚合有个 precision_threshold 参数,ES7.8.0 默认是3000,最大可配置成40000,也即:如果 es_agg_test 表里面不同 label 的记录超过4w,ES 统计出来的结果可能不准确。
二、terms 聚合
2.1 全部 label 聚合统计
有时候,知道一共有多少个不同的 label 还不够,还想知道每个 label 对应的行数(记录数)是多少?
在示例数据中,一共有5个不同的 label,我们统计出了所有的这5个 label 对应的行数(记录数)。
而有时候,往往需要的是 top N 统计,比如统计行数最多的前2个 label,在示例数据中,分别是 "mate30" 和 "iphone11"
SQL 写法:
select label,count((label)) from es_agg_test group by label;
输出的结果如下:
mate30,3
iphone11,2
iphone12,1
p30,1
mate20,1
相应地,ES 要实现统计每一个 label 对应的行数(记录数),可以通过 terms 聚合来实现。terms 聚合需要传一个 size 参数,具体到上面的示例,也即一个有多少个不同的 label,这可以通过 cardinality 聚合来得到。但是,需要注意 cardinality 聚合参数 precision_threshold 的限制。
2.2 top N label 聚合统计
如果只需要统计行数最多的前2个 label,那 size 参数如何设置呢?可能大家的第一反应就是 size 参数设置成2。由于 ES 底层是分布式存储,数据分散在不同的分片中,因此存在一个分布式统计的误差问题。如下 ES 索引有2个分片,每个分片上的记录数量如下。如果分片 top2 聚合,就会导致2种错误:
1、label 不正确。真正的 top2 label 是 "iphone11" 和 "mate20",但是分片 top2 聚合产生的结果是 "iphone11" 和 "mate30"
2、数量不正确。label 为 "iphone" 的行数应该是 510,但是聚合出来的结果是 500
正是因为分布式聚合统计存在如上问题,所以 ES 在 terms 聚合时,size 越大,聚合的结果越精确,但是性能开销也越大。
The higher the requested size is, the more accurate the results will be, but also, the more expensive it will be to compute the final results (both due to bigger priority queues that are managed on a shard level and due to bigger data transfers between the nodes and the client).
实际需求是求解 top2,但是若在每个分片上计算 topN 时,是按 top3 来统计的话,上面的示例计算出来的结果就和“上帝视角”保持一致了。这也是为什么 terms 聚合里面有个 shard_size 参数的原因,shard_size 的计算公式是:shard_size = (size * 1.5 + 10)
如果要计算 topN,在 ES 每个分片上计算的是 top (N*1.5+10),然后再汇总排序得出 topN。如果在求解 topN 过程中,导致 shard_size 参数超过了1万,ES7.8 就会报错:
Trying to create too many buckets. Must be less than or equal to: [10000] but was [10001]. This limit can be set by changing the [search.max_buckets] cluster level setting
shard_size 参数由 ES 的索引动态配置参数 search.max_buckets 参数限制,ES7.8.0 默认是 10000,参考:search.max_buckets 配置。
三、参考链接
1、https://stackoverflow.com/questions/57393548/control-number-of-buckets-created-in-an-aggregation