聚合:从指标到管道聚合
聚合(Aggregation):从指标到管道聚合
你在 ES 里不仅能搜数据,还能直接"问数据问题":
- 每个分类下有多少商品?
- 近 30 天订单金额的变化趋势?
- 哪些商品的评分最高?
- 用户行为中,从浏览到下单的转化漏斗长什么样?
这些 ES 全都能做,而且比"查出数据在应用层计算"快一到两个数量级。用的就是聚合(Aggregation)。
聚合的三大类
Metric(指标聚合) → 算数:avg、sum、min、max、stats
Bucket(桶聚合) → 分组:terms、range、date_histogram、histogram
Pipeline(管道聚合) → 对聚合结果再加工:移动平均、导数、百分位
Metric 聚合:对查询结果做数学运算
GET /orders/_search
{
"size": 0,
"aggs": {
"total_revenue": { "sum": { "field": "amount" } },
"avg_order_value": { "avg": { "field": "amount" } },
"max_order": { "max": { "field": "amount" } },
"stats": { "stats": { "field": "amount" } }
}
}
返回:
{
"aggregations": {
"total_revenue": { "value": 123456789.0 },
"avg_order_value": { "value": 4567.89 },
"max_order": { "value": 99999.0 },
"stats": { "count": 27000, "min": 1.0, "max": 99999.0, "avg": 4567.89, "sum": 123456789.0 }
}
}
注意 "size": 0——我们只关心聚合结果,不需要返回文档本身。
Cardinality:近似去重计数
{ "cardinality": { "field": "user_id" } }
注意:cardinality 是近似值(基于 HyperLogLog 算法),精度在 1-6% 误差范围内。如果需要精确去重,用 script 但性能代价很大。
Bucket 聚合:把文档装进不同的桶
Terms 聚合:按字段值分组(最常用)
GET /orders/_search
{
"size": 0,
"aggs": {
"orders_by_status": {
"terms": { "field": "status", "size": 20 }
}
}
}
结果:
{
"aggregations": {
"orders_by_status": {
"buckets": [
{ "key": "paid", "doc_count": 15000 },
{ "key": "pending", "doc_count": 8000 },
{ "key": "canceled", "doc_count": 3000 }
]
}
}
}
Date Histogram:按时间切片
GET /orders/_search
{
"size": 0,
"query": { "range": { "created_at": { "gte": "now-30d" } } },
"aggs": {
"orders_over_time": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day",
"format": "yyyy-MM-dd"
}
}
}
}
calendar_interval 选项:minute、hour、day、week、month、quarter、year。
也可以用 fixed_interval 按固定时长切:"fixed_interval": "2h"、"fixed_interval": "10m"。
Range 聚合:按数值区间分组
{
"aggs": {
"price_ranges": {
"range": {
"field": "amount",
"ranges": [
{ "to": 1000 },
{ "from": 1000, "to": 5000 },
{ "from": 5000, "to": 10000 },
{ "from": 10000 }
]
}
}
}
}
Histogram:自动等距分桶
{
"aggs": {
"price_histogram": {
"histogram": {
"field": "amount",
"interval": 500
}
}
}
}
与 range 不同的是不需要手动指定区间,ES 自动按 500 步长切。
嵌套聚合:Buckets + Metrics 组合拳
最实用的模式——先分组,再在每个组内做指标计算:
GET /orders/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category", "size": 10 },
"aggs": {
"revenue": { "sum": { "field": "amount" } },
"avg_price": { "avg": { "field": "amount" } },
"unique_users": { "cardinality": { "field": "user_id" } },
"orders_per_day": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
},
"aggs": {
"daily_revenue": { "sum": { "field": "amount" } }
}
}
}
}
}
}
这个查询一次性完成:按分类分组 → 每个分类的营收、均价、去重用户数、每日订单趋势。
也就是把前面所有的聚合类型像积木一样嵌套起来用。
Pipeline 聚合:对聚合结果再处理
Moving Average:移动平均
{
"aggs": {
"orders_per_day": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
},
"aggs": {
"daily_revenue": { "sum": { "field": "amount" } },
"smoothed_revenue": {
"moving_avg": {
"buckets_path": "daily_revenue",
"window": 7,
"model": "simple"
}
}
}
}
}
}
buckets_path: "daily_revenue" 告诉 ES:对每天的总和做 7 日移动平均。
Derivative:环比/同比
"daily_change": {
"derivative": {
"buckets_path": "daily_revenue"
}
}
每天相比前一天的变化值。
Bucket Sort:对聚合桶排序和分页
{
"aggs": {
"by_category": {
"terms": { "field": "category", "size": 100 },
"aggs": {
"revenue": { "sum": { "field": "amount" } },
"top_by_revenue": {
"bucket_sort": {
"sort": [{ "revenue": { "order": "desc" } }],
"size": 10
}
}
}
}
}
}
先按类别分组 → 每组计算营收 → 按营收倒排 → 只取 Top 10。
Terms 聚合的精准度陷阱
terms 聚合返回的 doc_count 是近似值,不是精确值。
原因是 ES 的 terms 聚合是在每个分片上取 top N,然后在协调节点上汇总再取 top N——这个过程会产生误差。
{
"terms": {
"field": "category",
"size": 10,
"shard_size": 100
}
}
size: 10— 最终返回 10 个桶shard_size: 100— 每个分片返回 100 个桶给协调节点
shard_size 越大越精确,但协调节点内存消耗也越大。建议设为 size * 5 以上。
如果不接受任何误差,把索引设为 1 个分片——但这样做失去了分片的扩展性。
总结
ES 聚合就是内置的分布式分析引擎:
Metric → count, sum, avg, min, max, stats, cardinality
Bucket → terms, range, date_histogram, histogram
Pipeline → moving_avg, derivative, bucket_sort, cumulative_sum
记住三个要点:
- 聚合用
size: 0,不返回文档——省带宽省序列化 - 嵌套聚合是核心玩法——先 bucket 分组,再 metric 算数,再 pipeline 加工
- terms 聚合不精确——设置足够大的
shard_size减少误差