ES 聚合分析深度解析
面试官:ES 的聚合用过吗?
你:用过,主要用 Terms 聚合做分组统计,avg/sum 做指标计算,嵌套聚合做多维度分析。
面试官:聚合为什么对内存要求高?如何优化?
链式追问一:聚合类型
Section titled “链式追问一:聚合类型”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 } } }}
// 等价 SQLSELECT 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" } } } } }}
// 等价 SQLSELECT 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 内存的权衡 } } }}链式追问二:聚合性能
Section titled “链式追问二:聚合性能”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 可提高精度,但增加网络传输和内存开销Q5:Pipeline 聚合的典型用法?
Section titled “Q5:Pipeline 聚合的典型用法?”移动平均(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" }}// 计算每天相对前一天的销售额变化量