ElasticsearchNotes

第 11 章:聚合分析

zjc 于 2026-01-11 发布

这是《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,可能被漏掉。

提高准确性:

  1. 增大 size
  2. 增大 shard_size
  3. 减少分片数量;
  4. 使用更适合的索引设计;
  5. 对关键报表离线精确计算。

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 高

优化路径:

  1. 缩小时间范围;
  2. 降低 terms size;
  3. 减少嵌套聚合;
  4. 预先计算派生字段;
  5. 使用 rollover 缩小单索引范围;
  6. 冷热分层;
  7. 大报表放数仓;
  8. 对高频报表做结果缓存;
  9. 限制并发看板查询;
  10. 压测聚合请求。

11.16 本章小结

11.17 思考题

  1. 为什么 terms 聚合可能漏掉部分低频词项?
  2. size=0 在聚合请求中有什么意义?
  3. 如何让品牌聚合不受当前品牌筛选影响?
  4. cardinality 为什么不适合金额统计?
  5. 一个看板聚合从 200ms 变成 8s,你会从哪些角度排查?