聚合:从指标到管道聚合

22 May 2026 – wusfe · 3 min read

聚合(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 选项:minutehourdayweekmonthquarteryear

也可以用 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

记住三个要点:

  1. 聚合用 size: 0,不返回文档——省带宽省序列化
  2. 嵌套聚合是核心玩法——先 bucket 分组,再 metric 算数,再 pipeline 加工
  3. terms 聚合不精确——设置足够大的 shard_size 减少误差