这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 搜索回答“哪些文档匹配”,聚合回答“这些文档有什么规律”。Elasticsearch 的聚合体系非常强大,也是性能问题的高发区。
本章覆盖 Bucket、Metric、Pipeline、嵌套聚合、排序、nested 聚合、近似算法和聚合性能基础。
11.1 聚合请求结构
只看聚合结果:
GET /orders/_search
{
"size": 0,
"query": {
"range": {
"created_at": { "gte": "now-30d/d" }
}
},
"aggs": {
"by_status": {
"terms": { "field": "status" }
},
"total_amount": {
"sum": { "field": "amount" }
}
}
}
size=0 表示不返回文档,只返回聚合结果。生产聚合应尽量加时间范围或其他过滤条件。
11.2 三类聚合
| 类型 | 作用 | 示例 |
|---|---|---|
| Bucket | 分桶 | 按状态、品牌、日期分组 |
| Metric | 计算指标 | sum、avg、max、cardinality |
| Pipeline | 基于其他聚合结果再计算 | derivative、cumulative_sum |
11.3 Metric 聚合
GET /orders/_search
{
"size": 0,
"aggs": {
"avg_amount": { "avg": { "field": "amount" } },
"max_amount": { "max": { "field": "amount" } },
"min_amount": { "min": { "field": "amount" } },
"sum_amount": { "sum": { "field": "amount" } },
"value_count": { "value_count": { "field": "amount" } },
"stats_amount": { "stats": { "field": "amount" } },
"percentiles_amount": {
"percentiles": {
"field": "amount",
"percents": [50, 90, 95, 99]
}
}
}
}
常用指标:
| 聚合 | 说明 |
|---|---|
avg |
平均值 |
sum |
总和 |
min / max |
最小/最大值 |
value_count |
数量 |
stats |
一次性返回多项基础指标 |
extended_stats |
更多统计量 |
percentiles |
分位数 |
percentile_ranks |
值所处百分位 |
cardinality |
去重计数,近似算法 |
top_hits |
桶内 Top N 文档 |
11.4 terms 聚合
GET /orders/_search
{
"size": 0,
"aggs": {
"by_brand": {
"terms": {
"field": "brand",
"size": 20,
"order": { "_count": "desc" }
}
}
}
}
按指标排序:
{
"aggs": {
"by_brand": {
"terms": {
"field": "brand",
"size": 20,
"order": { "total_sales": "desc" }
},
"aggs": {
"total_sales": {
"sum": { "field": "sales" }
}
}
}
}
}
terms 聚合是近似聚合。每个分片先取本地 Top N,再在协调节点归并。如果某个词项在每个分片都排不进本地前 N,可能被漏掉。
提高准确性:
- 增大
size; - 增大
shard_size; - 减少分片数量;
- 使用更适合的索引设计;
- 对关键报表离线精确计算。
11.5 filter 聚合
GET /orders/_search
{
"size": 0,
"aggs": {
"paid_orders": {
"filter": {
"term": { "status": "PAID" }
},
"aggs": {
"amount": { "sum": { "field": "amount" } }
}
}
}
}
多个互斥分组:
{
"aggs": {
"order_groups": {
"filters": {
"filters": {
"paid": { "term": { "status": "PAID" } },
"closed": { "term": { "status": "CLOSED" } },
"refunded": { "term": { "status": "REFUNDED" } }
}
}
}
}
}
11.6 range 聚合
GET /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 1000 },
{ "from": 1000, "to": 3000 },
{ "from": 3000, "to": 7000 },
{ "from": 7000 }
]
}
}
}
}
日期范围:
{
"aggs": {
"created_ranges": {
"range": {
"field": "created_at",
"format": "yyyy-MM-dd",
"ranges": [
{ "to": "2026-01-01" },
{ "from": "2026-01-01", "to": "2026-07-01" },
{ "from": "2026-07-01" }
]
}
}
}
}
11.7 histogram 与 date_histogram
直方图:
{
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 1000,
"min_doc_count": 0
}
}
}
}
按天统计:
GET /orders/_search
{
"size": 0,
"aggs": {
"daily_orders": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day",
"format": "yyyy-MM-dd",
"time_zone": "+08:00",
"min_doc_count": 0
}
}
}
}
| 参数 | 特点 | 示例 |
|---|---|---|
calendar_interval |
日历感知 | 1 day、1 month、1 quarter |
fixed_interval |
固定毫秒数 | 30s、1h、7d |
月份长度不同,应使用 calendar_interval: "month",不要用 fixed_interval: "30d" 代替自然月。
11.8 嵌套聚合
按品牌分组,再统计每个品牌的类目:
GET /products/_search
{
"size": 0,
"aggs": {
"brands": {
"terms": { "field": "brand", "size": 10 },
"aggs": {
"categories": {
"terms": { "field": "category", "size": 10 }
},
"avg_price": {
"avg": { "field": "price" }
}
}
}
}
}
日期加状态:
{
"aggs": {
"daily": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
},
"aggs": {
"status": {
"terms": { "field": "status" }
},
"gmv": {
"sum": { "field": "amount" }
}
}
}
}
}
嵌套层级越多,内存和 CPU 消耗越大。交互式看板建议控制聚合层级和桶数量。
11.9 cardinality 去重
GET /events/_search
{
"size": 0,
"aggs": {
"unique_users": {
"cardinality": {
"field": "user_id",
"precision_threshold": 40000
}
}
}
}
cardinality 使用 HyperLogLog++ 近似算法。precision_threshold 越高越准确,但内存开销越大。
适合 UV、独立设备数等趋势统计;财务、库存等精确指标不要使用近似算法。
11.10 top_hits
GET /orders/_search
{
"size": 0,
"aggs": {
"by_user": {
"terms": {
"field": "user_id",
"size": 10,
"order": { "total_amount": "desc" }
},
"aggs": {
"total_amount": {
"sum": { "field": "amount" }
},
"latest_orders": {
"top_hits": {
"size": 3,
"sort": [
{ "created_at": { "order": "desc" } }
],
"_source": ["order_id", "amount", "created_at"]
}
}
}
}
}
}
适合“每个分组取前 N 条”。top_hits 需要取回文档内容,开销比纯 Metric 聚合高。
11.11 Pipeline 聚合
按天累计销售额:
GET /orders/_search
{
"size": 0,
"aggs": {
"daily": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
},
"aggs": {
"gmv": {
"sum": { "field": "amount" }
},
"cumulative_gmv": {
"cumulative_sum": {
"buckets_path": "gmv"
}
}
}
}
}
}
环比:
{
"aggs": {
"daily": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
},
"aggs": {
"gmv": { "sum": { "field": "amount" } },
"daily_derivative": {
"derivative": { "buckets_path": "gmv" }
}
}
}
}
}
常用 Pipeline:
| 聚合 | 作用 |
|---|---|
derivative |
差值 |
cumulative_sum |
累计求和 |
moving_fn |
移动函数 |
bucket_script |
桶内脚本计算 |
bucket_selector |
过滤桶 |
bucket_sort |
桶排序 |
stats_bucket |
对桶指标统计 |
11.12 nested 聚合
统计 SKU 颜色:
GET /products/_search
{
"size": 0,
"aggs": {
"skus": {
"nested": { "path": "skus" },
"aggs": {
"colors": {
"terms": { "field": "skus.color" }
}
}
}
}
}
从 nested 结果回到父文档:
{
"aggs": {
"skus": {
"nested": { "path": "skus" },
"aggs": {
"colors": {
"terms": { "field": "skus.color" },
"aggs": {
"back_to_product": {
"reverse_nested": {},
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
}
}
}
}
}
11.13 post_filter
常见搜索需求是:文档结果受品牌过滤影响,但品牌聚合不受该过滤影响。
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "笔记本" } }
],
"filter": [
{ "term": { "status": "ON_SALE" } }
]
}
},
"aggs": {
"brands": {
"terms": { "field": "brand" }
}
},
"post_filter": {
"term": { "brand": "NOVA" }
}
}
post_filter 在查询和聚合后执行,只影响返回文档,不影响聚合结果。
11.14 composite 聚合分页
terms 聚合没有通用深分页。可以使用 composite 游标:
GET /orders/_search
{
"size": 0,
"aggs": {
"composite_buckets": {
"composite": {
"size": 100,
"sources": [
{ "brand": { "terms": { "field": "brand" } } },
{ "status": { "terms": { "field": "status" } } }
]
}
}
}
}
带 after_key 继续:
{
"aggs": {
"composite_buckets": {
"composite": {
"size": 100,
"sources": [
{ "brand": { "terms": { "field": "brand" } } },
{ "status": { "terms": { "field": "status" } } }
],
"after": {
"brand": "NOVA",
"status": "PAID"
}
}
}
}
}
11.15 聚合性能因素
| 因素 | 影响 |
|---|---|
| 分片数 | 分片越多,归并开销越大 |
| 桶数量 | 桶越多,内存和 CPU 越高 |
| 高基数字段 | terms/cardinality 成本高 |
| 时间范围 | 范围越大扫描越多 |
| nested 聚合 | 需要处理隐藏子文档 |
| top_hits | 取回文档,开销高 |
| 脚本 | 逐文档计算 |
| 冷数据 | Page Cache 未命中,磁盘 IO 高 |
优化路径:
- 缩小时间范围;
- 降低 terms size;
- 减少嵌套聚合;
- 预先计算派生字段;
- 使用 rollover 缩小单索引范围;
- 冷热分层;
- 大报表放数仓;
- 对高频报表做结果缓存;
- 限制并发看板查询;
- 压测聚合请求。
11.16 本章小结
- Bucket 分桶,Metric 计算指标,Pipeline 对聚合结果再计算;
- terms 是近似聚合,结果受 shard size 和桶数量影响;
- date_histogram 应区分 calendar interval 和 fixed interval;
- cardinality 是近似去重,不适合财务精确统计;
- nested 聚合需要显式进入 nested path;
- post_filter 只影响文档结果,不影响聚合;
- 聚合性能取决于分片数、桶数、时间范围、高基数字段和缓存命中。
11.17 思考题
- 为什么 terms 聚合可能漏掉部分低频词项?
size=0在聚合请求中有什么意义?- 如何让品牌聚合不受当前品牌筛选影响?
- cardinality 为什么不适合金额统计?
- 一个看板聚合从 200ms 变成 8s,你会从哪些角度排查?