Skip to content

ES 聚合分析深度解析

面试官:ES 的聚合用过吗?

:用过,主要用 Terms 聚合做分组统计,avg/sum 做指标计算,嵌套聚合做多维度分析。

面试官:聚合为什么对内存要求高?如何优化?


Q1:ES 聚合有哪几类?高频

Section titled “Q1:ES 聚合有哪几类?”

三大类聚合

类型代表功能
Bucket(桶)聚合terms, date_histogram, range, nested将文档分组到桶中(类似 SQL GROUP BY)
Metric(指标)聚合avg, sum, max, min, cardinality, percentiles对桶内文档计算统计指标
Pipeline(管道)聚合bucket_sort, derivative, moving_avg对其他聚合的结果进行二次计算

Q2:常见聚合的写法和对应 SQL?常考

Section titled “Q2:常见聚合的写法和对应 SQL?”

Terms 聚合(等价 GROUP BY COUNT)

// ES DSL
{
"aggs": {
"by_category": {
"terms": {
"field": "category.keyword",
"size": 10
}
}
}
}
// 等价 SQL
SELECT category, COUNT(*) FROM products GROUP BY category LIMIT 10;

嵌套聚合(GROUP BY + AVG)

// ES DSL
{
"aggs": {
"by_category": {
"terms": { "field": "category.keyword" },
"aggs": {
"avg_price": {
"avg": { "field": "price" }
}
}
}
}
}
// 等价 SQL
SELECT category, AVG(price) FROM products GROUP BY category;

Date Histogram(时间分组)

{
"aggs": {
"sales_by_day": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day",
"format": "yyyy-MM-dd"
},
"aggs": {
"total_amount": {
"sum": { "field": "amount" }
}
}
}
}
}
// 等价:按天统计销售总额

Cardinality(去重计数,类似 COUNT DISTINCT)

{
"aggs": {
"unique_users": {
"cardinality": {
"field": "user_id",
"precision_threshold": 100 // 精度 vs 内存的权衡
}
}
}
}

Q3:ES 聚合为什么对内存要求高?高频

Section titled “Q3:ES 聚合为什么对内存要求高?”

聚合的内存消耗原理

Terms 聚合对 user_id 字段分组:
ES 需要将所有文档的 user_id 字段值加载到内存
构建 user_id → [docId, docId, ...] 的映射(field data)
假设 1 亿文档,user_id 平均 10 字节
→ 约 1GB 的 field data 内存

Field Data vs Doc Values

机制加载时机内存位置适用字段
Field Data查询时动态加载到 JVM 堆JVM 堆内存(GC 影响大)text 类型(不推荐)
Doc Values索引时预先构建,存磁盘操作系统页缓存(堆外)keyword, numeric, date(默认开启)

最佳实践

  • 需要聚合的字段使用 keyword 类型(而非 text
  • keyword 字段自动使用 Doc Values,不占 JVM 堆
  • 聚合字段禁用 _source(如只用于统计,不需要返回原始值)

Q4:Terms 聚合的结果不准确是怎么回事?高频

Section titled “Q4:Terms 聚合的结果不准确是怎么回事?”

问题背景:Terms 聚合在分布式场景下可能返回不精确的结果。

根本原因

查询 Top 3 分类(size=3),集群有 3 个分片:
每个分片各自返回本地 Top 3
Shard 1: [A:100, B:80, C:60]
Shard 2: [A:90, D:70, E:50]
Shard 3: [B:100, C:90, F:40]
Coordinating 节点汇总(只有每个分片返回的数据):
A: 100+90 = 190
B: 80+100 = 180
C: 60+90 = 150
D: 70(Shard 3 没返回 D,但 D 在 Shard 3 可能有数量!)
→ 结果可能遗漏真正的 Top 3

解决方案

{
"aggs": {
"by_category": {
"terms": {
"field": "category.keyword",
"size": 10,
"shard_size": 50 // 每个分片返回 Top 50,汇总更准确
}
}
}
}
// shard_size 默认是 size × 1.5 + 10
// 增大 shard_size 可提高精度,但增加网络传输和内存开销

移动平均(moving_avg

{
"aggs": {
"sales_by_day": {
"date_histogram": {
"field": "date",
"calendar_interval": "day"
},
"aggs": {
"daily_sales": {
"sum": { "field": "amount" }
},
"7day_moving_avg": {
"moving_avg": {
"buckets_path": "daily_sales",
"window": 7
}
}
}
}
}
}
// 计算每天销售额的 7 日移动平均

环比计算(derivative

"daily_growth": {
"derivative": {
"buckets_path": "daily_sales"
}
}
// 计算每天相对前一天的销售额变化量