聚合是 Elasticsearch 除检索外的另一半能力:检索回答「哪些文档命中」,聚合回答「这些文档呈现什么分布」。本文覆盖 metric、bucket、pipeline 三类聚合,讲解嵌套聚合与日期直方图的用法,并给出对接 Kibana 可视化的最佳实践。
1. 聚合分析基础
一句话总结: 聚合把命中的文档集合按统计口径折叠成指标与分组,与 query 搭配完成分析与下钻。
1.1 聚合的定位与结构
聚合(Aggregation)紧跟在 query 之后执行:先由 query 筛出候选文档,再对候选集合做统计。请求体中聚合与查询并列,一条请求既能拿到搜索结果又能拿到统计结果。
| 聚合类型 | 作用 | 输出 | 常见例子 |
|---|---|---|---|
| metric | 单值统计 | 一个数值 | avg、sum、max、cardinality |
| bucket | 分组 | 一组桶 | terms、range、date_histogram |
| pipeline | 跨桶计算 | 对桶再加工 | avg_bucket、bucket_script |
1.2 最小的聚合请求
{
"size": 0,
"aggs": {
"avg_price": {
"avg": { "field": "price" }
}
}
}
size 设为 0 只返回聚合不返回命中文档,能显著降低传输与解析开销,纯分析场景应默认如此。响应中的 aggregations 与 hits 并列,每个聚合以自定义名字(此处 avg_price)为键返回。
2. metric 聚合
一句话总结: metric 聚合把字段折叠成单值或多值指标,是最底层的统计单元。
2.1 单值指标
{
"size": 0,
"aggs": {
"total_amount": { "sum": { "field": "amount" } },
"avg_amount": { "avg": { "field": "amount" } },
"max_amount": { "max": { "field": "amount" } },
"min_amount": { "min": { "field": "amount" } },
"count": { "value_count": { "field": "amount" } }
}
}
sum、avg、max、min 对数值字段做简单统计,value_count 统计字段非空的文档数,可用于计算均值之外的基数校验。所有指标基于 Doc Values 读取,无需加载 _source。
2.2 stats 与 percentiles
{
"size": 0,
"aggs": {
"price_stats": { "stats": { "field": "price" } },
"price_percentiles": {
"percentiles": { "field": "price", "percents": [50, 90, 95, 99] }
}
}
}
stats 一次返回 count/min/max/avg/sum 五个指标,percentiles 输出分位数,适合衡量响应时间与价格分布。分位数默认用 TDigest 算法,内存占用远小于全量排序,但对极端值存在近似误差,精度与内存可调。
2.3 cardinality 去重基数
{
"size": 0,
"aggs": {
"unique_users": {
"cardinality": { "field": "user_id", "precision_threshold": 1000 }
}
}
}
cardinality 用 HyperLogLog 近似去重计数,precision_threshold 控制精确与内存的平衡,默认 3000、上限 40000。近似基数统计在亿级 UV 场景误差在 1% 以内,远比精确 Set 省内存。
3. bucket 聚合
一句话总结: bucket 聚合把文档分入各桶,是分组的骨架,也是嵌套聚合与下钻的起点。
3.1 terms 分组
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category", "size": 10, "order": { "_count": "desc" } }
}
}
}
terms 按字段值分组,size 控制返回桶数而非分桶总数,默认返回按文档数降序的前 10 桶。terms 只能在 keyword 或启用 fielddata 的字段上分组;text 字段必须用 keyword 子字段。size 设得越大,查询越慢、内存越高,前几桶够用时不必拉满。
3.2 range 与 date_range
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 500 },
{ "from": 500 }
]
}
}
}
}
range 按数值区间分桶,边界遵循 to 开区间、from 闭区间的约定,多段区间常用来生成价格分布直方图。date_range 用法相同,只是字段是日期。
3.3 histogram 数值直方图
{
"size": 0,
"aggs": {
"amount_histogram": {
"histogram": { "field": "amount", "interval": 50 }
}
}
}
histogram 按固定步长生成等宽直方图桶,适合看金额、时长的分布形态。interval 过小会生成海量空桶或稀疏桶,先做 percentile 摸清量级再定步长。
4. 嵌套聚合
一句话总结: 嵌套聚合在桶内再做子聚合,实现「分组后再统计」,是下钻分析的核心结构。
4.1 桶内统计
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category", "size": 5 },
"aggs": {
"avg_amount": { "avg": { "field": "amount" } },
"order_count": { "value_count": { "field": "order_id" } }
}
}
}
}
terms 内嵌 aggs 后,每个分类桶内分别计算平均金额与订单数,响应是两级嵌套结构。嵌套深度与桶数量线性放大计算量,三层以上桶数往往爆炸,务必用 size 与 filter 收口。
4.2 多层下钻
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category", "size": 5 },
"aggs": {
"by_brand": {
"terms": { "field": "brand", "size": 5 },
"aggs": {
"max_sales": { "max": { "field": "sales" } }
}
}
}
}
}
}
类目下再按品牌细分,最后统计品牌内最大销量,即多维下钻的经典形态:类目 → 品牌 → 指标。桶数按 5 × 5 增长,设计下钻路径时始终问一句「每一层是否真的需要全部桶」。
4.3 filter 与 filters 桶
{
"size": 0,
"aggs": {
"high_value": {
"filter": { "range": { "amount": { "gte": 500 } } },
"aggs": {
"avg_amount": { "avg": { "field": "amount" } }
}
}
}
}
filter 桶先过滤再统计,相当于对子集做聚合;filters 桶则一次给出多个过滤条件的对比统计,常用于把 VIP、普通、退款三类订单分开比较。
5. pipeline 聚合
一句话总结: pipeline 聚合消费其他聚合的输出桶做二次计算,实现均值嵌套、比率与滑动平均。
5.1 avg_bucket 与 bucket_script
{
"size": 0,
"aggs": {
"orders_per_day": {
"date_histogram": {
"field": "create_time",
"calendar_interval": "day"
},
"aggs": {
"daily_total": { "sum": { "field": "amount" } }
}
},
"avg_daily_total": {
"avg_bucket": { "buckets_path": "orders_per_day>daily_total" }
}
}
}
avg_bucket 对子聚合产生的桶值再求均值,buckets_path 用 > 号引用嵌套路径,是求「每日平均销售额」这类复合指标的标准写法。bucket_script 则对两个桶值做四则运算,例如算点击率 click / (click + view)。
5.2 moving_avg 滑动平均
{
"size": 0,
"aggs": {
"sales_per_day": {
"date_histogram": {
"field": "create_time",
"calendar_interval": "day"
},
"aggs": {
"daily_total": { "sum": { "field": "amount" } },
"smooth_total": {
"moving_avg": { "buckets_path": "daily_total", "window": 7 }
}
}
}
}
}
moving_avg 对前 window 个桶做滑动平均,平滑日粒度数据的抖动,适合看趋势。滑动平均依赖桶的有序性,date_histogram 天然有序,terms 桶需要显式排序才能用于 pipeline。
5.3 pipeline 的三种形式
pipeline 聚合分成三类:父聚合(avg_bucket、sum_bucket)依赖同一级子聚合的输出;兄弟聚合(bucket_script、bucket_selector)在同级聚合中引用其他兄弟;另有 percentiles_bucket 等按桶聚合输出。bucket_selector 常用来过滤掉异常桶,例如只保留日销售额超过阈值的日子。
6. 日期直方图与时序统计
一句话总结: date_histogram 把事件按时间分桶,是日志分析与时序统计的地基。
6.1 calendar_interval 与 fixed_interval
{
"size": 0,
"aggs": {
"by_hour": {
"date_histogram": {
"field": "create_time",
"calendar_interval": "hour",
"time_zone": "+08:00"
}
}
}
}
calendar_interval 支持 second/minute/hour/day/week/month/quarter/year 等日历粒度,month 会自动对齐月首;fixed_interval 支持任意毫秒/秒/分钟数,如 90m。跨时区的日分桶必须显式指定 time_zone,否则按 UTC 对齐造成「上午数据被算进前一天」。
6.2 缺桶与偏移
{
"size": 0,
"aggs": {
"by_day": {
"date_histogram": {
"field": "create_time",
"calendar_interval": "day",
"min_doc_count": 0,
"offset": "+8h"
}
}
}
}
min_doc_count 为 0 时补出没有文档的空桶,让折线连续;offset 平移桶边界,例如把「自然日」切成「结算日」。补空桶会增加响应体积,聚合跨度很大时建议在应用层补 0 而非让 ES 全量输出。
6.3 基于时序的环比
{
"size": 0,
"aggs": {
"today": {
"filter": { "range": { "create_time": { "gte": "2026-09-30T00:00:00+08:00", "lt": "2026-10-01T00:00:00+08:00" } } },
"aggs": { "total": { "sum": { "field": "amount" } } }
},
"yesterday": {
"filter": { "range": { "create_time": { "gte": "2026-09-29T00:00:00+08:00", "lt": "2026-09-30T00:00:00+08:00" } } },
"aggs": { "total": { "sum": { "field": "amount" } } }
}
}
}
环比可直接写成两个 filter 桶,各自统计后交给应用算增长率;需要同比时同理补上月同日区间。数据量极大时可把常用统计预聚合到汇总索引,避免每次全量扫分片。
7. Kibana 可视化对接
一句话总结: Kibana 把聚合响应渲染成图表,聚合设计直接决定可视化能画什么。
7.1 从聚合到图表
Kibana Lens 与 Discover 的每个可视化背后都是一条带 aggs 的搜索请求:折线图对应 date_histogram + metric,柱状图对应 terms + metric,饼图对应 terms 占比。设计聚合时想清楚「X 轴是什么桶、Y 轴是什么指标」,图表能力就定了。
{
"size": 0,
"aggs": {
"date_buckets": {
"date_histogram": { "field": "create_time", "calendar_interval": "day" },
"aggs": {
"amount_sum": { "sum": { "field": "amount" } }
}
}
}
}
7.2 数据源与仪表盘
Kibana 通过数据视图(Data View)绑定索引模式,把字段类型映射成可聚合字段。数值字段在可视化中自动提供 sum/avg 等指标选项,keyword 字段提供 terms 分桶。仪表盘上的多个面板共用同一数据视图时,聚合口径要一致,避免「图表 A 用 doc 计数、图表 B 用 sum」造成的口径混乱。
7.3 聚合性能红线
| 场景 | 风险 | 缓解 |
|---|---|---|
| terms size 过大 | 内存飙升、结果不准 | 用 top hits 替代或加 order 约束 |
| 嵌套三层以上 | 桶数量爆炸 | 收敛 size、前置 filter |
| 大跨度 date_histogram | 空桶海量 | min_doc_count=0 慎用 |
| cardinality 大基数 | HLL 近似误差 | 提高 precision_threshold |
| 每次全量扫分片 | 查询变慢 | 预聚合汇总索引 |
聚合请求应在 Kibana 的 Request Inspector 中确认实际发送的 DSL,指标聚合优先走 Doc Values,避免使用需要加载 _source 的字段。
8. 总结
一句话总结: 聚合分析以 metric 为指标、bucket 为分组、pipeline 为跨桶加工,配合日期直方图与 Kibana 完成从指标统计到多维下钻的完整分析闭环。
| 环节 | 要点 |
|---|---|
| 聚合结构 | query 先筛后聚,size=0 纯分析 |
| metric | avg/sum/stats/percentiles/cardinality 单层统计 |
| bucket | terms/range/histogram 分组骨架 |
| 嵌套聚合 | 桶内再聚,多维下钻 |
| pipeline | avg_bucket/bucket_script 跨桶加工 |
| 时序统计 | date_histogram 指定日历粒度与时区 |
| Kibana 对接 | 可视化背后即聚合请求,口径需一致 |
| 性能红线 | 桶数收敛、嵌套受限、避免全量扫描 |
聚合把散落的日志与业务数据折叠成可读的指标和分组。先明确分析口径,再选对聚合类型,最后用 Kibana 把结果画成图表;当聚合成为瓶颈时,优先考虑预聚合索引与收敛桶规模。聚合语法的上游可阅读《倒排索引与分词原理》与《Query DSL 与相关性打分》,索引与字段设计参考《数据建模与 Mapping 设计》。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。