大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
聚合分析:Metrics、Bucket 与 Pipeline
这一课你将能做到
- 区分指标聚合(metrics)与桶聚合(bucket),写出题库常用的 avg/max/stats 与 terms/date_histogram
- 用嵌套聚合实现「按课程分桶 → 桶内按标签分桶 → 统计平均难度」的多层分析
- 使用管道聚合(pipeline)做桶间的派生计算(累计、环比),并知道聚合性能边界在哪里
为什么要学
ES 不只是搜索引擎,也是分析引擎。运营看板上的问题——「每个课程有多少题、平均难度多少、本月新增多少题、哪些标签题目最多」——不需要再把数据捞到 MySQL 里 GROUP BY。聚合直接在倒排索引的列式结构(doc_values)上计算,亿级数据也能秒级出结果,Kibana 的图表底层全是聚合 DSL。
人话:metrics 回答「一个数」(平均多少、最大多少);bucket 回答「分成几组,每组怎样」(先按课程分组,再算每组);pipeline 回答「基于其他聚合结果再算」(累计值、差值)。三者组合就是一个报表后端。
先建立图像
聚合分类:
指标聚合(对文档算值):
avg / sum / min / max / stats / cardinality(去重计数)/ percentiles
桶聚合(把文档分组):
terms 按词项分组(标签、课程)
date_histogram 按时间区间分组(每天/每月)
histogram 按数值区间分组(难度区间)
range 按自定义区间
管道聚合(对其他聚合结果再算):
derivative 相邻桶差值(环比)
cumulative_sum 累计求和
bucket_script 桶级表达式
avg_bucket 对桶结果求均值
执行顺序:先按 query 圈定文档,再对这些文档同时算聚合;桶里可以嵌套子聚合,形成树。
跟着做
步骤 1:指标聚合——题库整体情况
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"size": 0,
"aggs": {
"avg_difficulty": { "avg": { "field": "difficulty" } },
"max_difficulty": { "max": { "field": "difficulty" } },
"difficulty_stats": { "stats": { "field": "difficulty" } },
"tag_count": { "cardinality": { "field": "tags" } }
}
}'
要点:size: 0 表示不要文档、只要聚合结果;cardinality 是 HyperLogLog 近似去重,省内存,小数据量误差为零,海量时有可配置误差——做「标签有多少种」足够。
stats 一次返回 count/min/max/avg/sum 五个值;想要方差、标准差用 extended_stats。
步骤 2:terms 桶聚合——每个标签多少题
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"size": 0,
"aggs": {
"by_tag": {
"terms": {
"field": "tags",
"size": 10,
"order": { "_count": "desc" }
}
}
}
}'
注意:聚合字段是 keyword 类型(对 text 聚合会出问题,第 4 课讲过)。terms 聚合返回的是近似 Top N:每个分片各取自己的 Top N 再合并,N 之外的长尾可能丢失。需要精确可加大 size(有内存成本),或用 composite 聚合做全量分页。
步骤 3:多层嵌套——课程 → 标签 → 难度
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"size": 0,
"query": { "term": { "published": true } },
"aggs": {
"by_course": {
"terms": { "field": "courseId", "size": 20 },
"aggs": {
"avg_diff": { "avg": { "field": "difficulty" } },
"by_tag": {
"terms": { "field": "tags", "size": 5 },
"aggs": {
"tag_avg_diff": { "avg": { "field": "difficulty" } }
}
}
}
}
}
}'
读结果:每个课程桶里有平均难度、标签 Top5;每个标签桶里还有该标签题目的平均难度。嵌套聚合天然回答「组内再分组」的问题,不需要 JOIN。
步骤 4:date_histogram——题目更新趋势
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"size": 0,
"aggs": {
"monthly": {
"date_histogram": {
"field": "updatedAt",
"calendar_interval": "month",
"format": "yyyy-MM",
"min_doc_count": 0,
"extended_bounds": {
"min": "2026-01",
"max": "2026-10"
}
},
"aggs": {
"avg_diff": { "avg": { "field": "difficulty" } }
}
}
}
}'
min_doc_count: 0 让没有数据的月份也返回空桶(画折线图时不能断档);extended_bounds 固定起止范围。注意 date_histogram 桶边界默认按 UTC,国内环境可用 time_zone: "+08:00" 修正月度归属。
步骤 5:管道聚合——月度累计与环比
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"size": 0,
"aggs": {
"monthly": {
"date_histogram": {
"field": "updatedAt",
"calendar_interval": "month"
},
"aggs": {
"new_count": { "value_count": { "field": "_id" } },
"cumulative": {
"cumulative_sum": { "buckets_path": "new_count" }
},
"mom": {
"derivative": { "buckets_path": "new_count" }
}
}
}
}
}'
buckets_path 指向同级或父级的某个聚合结果。derivative 第一桶没有值(没有上月可比),从第二桶开始输出差值。bucket_script 可写表达式,例如计算两桶相除的百分比。
步骤 6:nested 聚合与 composite 全量分页
对 nested 评论字段统计评论者:
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"size": 0,
"aggs": {
"comments_agg": {
"nested": { "path": "comments" },
"aggs": {
"by_user": {
"terms": { "field": "comments.user", "size": 10 }
}
}
}
}
}'
要把所有「课程 × 标签」组合无遗漏导出,用 composite 聚合,它像 search_after 一样用 after_key 翻页:
"aggs": {
"course_tag": {
"composite": {
"size": 100,
"sources": [
{ "course": { "terms": { "field": "courseId" } } },
{ "tag": { "terms": { "field": "tags" } } }
]
}
}
}
讲清楚要点
- 聚合算在 doc_values 上,不靠倒排扫描:聚合期间文档逐条读列值,列存对这种访问极友好。聚合字段必须是 keyword/数值/date——再次回到字段设计。
- terms 聚合是近似算法:多分片下取分片 Top N 合并,长尾可能漏。看板展示用默认即可;精确统计调大 size 或换 composite,并关注内存(
terms的 size 过大会占很多桶内存)。 - query 决定聚合的数据集:聚合只算命中文档,所以「某课程的标签分布」就是 query 加 term 过滤,不需要在聚合里再筛。filter 不打分开销低,适合聚合前过滤。
- 桶聚合 + 指标子聚合是主力范式:
GROUP BY 字段= terms/histogram 桶;AVG/COUNT(*)= 桶内 metrics。SQL 思维可以平移。 - 聚合深度与基数的内存风险:桶数量 = 各层基数乘积,百万级高基数字段多层嵌套会撑爆内存。生产大聚合用 breadth_first 收集模式、限制 size,必要时用 rollup(预聚合)或 Transform 物化结果。
你可能会懵的地方
- 为什么聚合结果里桶的数量和我预期不一致? 近似算法+默认 size=10。先加大 size;确认分片数与数据分布;需要全量精确结果用 composite。
- cardinality 算出来的数有时少 1? HyperLogLog 是概率近似结构,默认精度下误差通常 <1%,对「UV、标签数」类统计完全可接受;要更准调 precision_threshold(有内存代价)。
- date_histogram 按「月」分桶,月初的桶里混了上月最后一天?
时区问题,桶边界按 UTC 计算。加
time_zone: "+08:00"。 - 管道聚合为什么报「could not find buckets_path」?
路径写错或作用域不对:兄弟聚合写名字、父级写
父名>子名;被引用的聚合必须已按顺序计算。 - 聚合很慢是不是该加机器? 先看桶基数和时间范围:扫描了多少文档、桶是不是几百万个。缩范围、限桶数、预聚合往往比加机器有效。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 对 text 字段做 terms 聚合 | fielddata 警告、结果按词碎开 | 用 keyword 字段 |
| terms size 默认 10 却以为取了全部 | 长尾丢失 | 按需要设 size/composite |
| 聚合接口还 size:10 取文档 | 白做文档查询 | size: 0 |
| date_histogram 忘设时区 | 月度归属偏移 | time_zone 参数 |
| 高基数字段多层嵌套聚合 | 桶爆炸、内存溢出 | 限桶数、Transform、rollup |
| 把 cardinality 当精确去重 | 有微小误差 | 精确场景换 terms+composite |
卡点急救
- 聚合 OOM 或请求被熔断(circuit breaker):立刻减小 terms 的 size、缩短时间范围;开启
collect_mode: breadth_first减少早期桶数;长期方案是预聚合。 - 桶统计数量不准:检查分片数导致的近似误差,调大 shard_size;对比 composite 全量结果校准。
- 聚合响应巨大拖慢接口:只在前端图表所需层级返回;把明细与汇总拆两个接口;
rest_total_hits_as_int等无关参数不要加。
面试常问
-
ES 聚合有哪些类型? 三类:指标聚合对文档计算 avg/sum/max/cardinality 等数值;桶聚合按 terms、histogram、date_histogram 等把文档分组,桶内可嵌套子聚合;管道聚合基于其他聚合的输出做 derivative、cumulative_sum、bucket_script 等派生计算。底层依赖 doc_values 列式结构。
-
terms 聚合为什么可能不精确?怎么解决? 协调节点让各分片独立返回 Top N 再合并,分片上排在 N 名之外的词项无法参与全局排序,长尾可能遗漏。可以调大 size/shard_size 提升准确性,或用 composite 聚合配合 after_key 做全量遍历,代价是更多内存与请求。
-
聚合查询很慢怎么优化? 先用 query/filter 收窄文档范围和时间跨度;控制桶基数,高基数场景采用 breadth_first;只取需要的聚合层级;频繁运行的重聚合用 Rollup 或 Transform 预计算;必要时把聚合与写入节点物理隔离。
自测三问
- 「按课程统计题目数和平均难度,再取题量 Top3 课程」分别用哪几种聚合组合?
- terms 聚合在 5 个分片下为什么会漏词项?什么 API 能保证全量遍历?
- pipeline 聚合的输入是什么?这决定了它为什么不能独立存在?
小结一句话
聚合把搜索变成分析:metrics 算数、bucket 分组、pipeline 做派生,核心约束是字段要有 doc_values、桶基数要可控,近似算法够用时不必追求精确。
延伸
- Aggregations
- Terms aggregation
- Pipeline aggregations
- 下一课:向量与语义搜索——当用户不按关键词提问时,kNN 如何靠语义找题。