1.9 Elasticsearch-轻量聚合:terms、avg、max、min

在这里插入图片描述
1.9 Elasticsearch-轻量聚合:terms、avg、max、min
——在“搜”完之后立刻“算”,不拖慢毫秒级响应


0 为什么需要“轻量聚合”

在日志、指标、商品、订单等典型场景中,前端往往要在返回命中结果的同时,把“分布”“极值”“均值”一并带出。如果把这些计算下推到应用层,就意味着一次搜索请求后要拉回全部明细数据——带宽、内存、GC 都会爆炸。Elasticsearch 把聚合(aggregation)做成和搜索同一套 DSL、同一个倒排索引、同一套分布式执行框架,使得“搜+算”在毫秒级完成。对 99% 的线上场景,我们不需要写脚本、不需要 MapReduce,只要记住四个最常用、最轻量的聚合入口:terms、avg、max、min。本节就围绕它们展开,给出语法、注意点、性能陷阱与调优技巧。


1 terms:分组统计,一行 DSL 出柱状图

1.1 基础语法
GET /order-2025-11/_search
{
  "size": 0, 
  "aggs": {
    "status_dist": {
      "terms": {
        "field": "status.keyword",  
        "size": 10                  
      }
    }
  }
}
  • size=0 表示不返回命中文档,只要聚合结果
  • terms 聚合会按照 doc_count 降序返回前 10 个桶(bucket)

返回片段:

"aggregations": {
  "status_dist": {
    "buckets": [
      { "key": "paid",      "doc_count": 284731 },
      { "key": "canceled",  "doc_count":  41209 }
    ]
  }
}

前端可直接把 key 当 X 轴、doc_count 当 Y 轴画柱状图。

1.2 精准度陷阱:shard_size 与最小计数

terms 是分布式聚合:每个分片先本地取 Top N,再由协调节点二次归并。如果分片之间数据分布倾斜,归并结果可能漏掉真正的全局 Top N。

  • 官方默认 size=10 时,shard_size=size×1.5+10,可手动调大:
"terms": {
  "field": "ip",
  "size": 100,
  "shard_size": 1000
}
  • 对绝对精准场景(如财务对账)用 composite 聚合分页遍历,或把 size 设成 2147483647(全量)并打开 execution_hint:map,但会牺牲内存。
1.3 排序与指标内嵌

terms 桶内部还能再挂子聚合,实现“分组后算均值/最大/最小”:

"aggs": {
  "status_dist": {
    "terms": {
      "field": "status.keyword",
      "order": { "avg_amount": "desc" } 
    },
    "aggs": {
      "avg_amount": { "avg": { "field": "amount" } }
    }
  }
}

协调节点会先生成桶,再按子聚合结果重排桶顺序,同样走内存堆。


2 avg:均值聚合,一行搞定

"aggs": {
  "avg_latency": {
    "avg": {
      "field": "latency"   
    }
  }
}
  • 支持 missing 参数指定缺省值,避免空桶被忽略:
"avg": {
  "field": "score",
  "missing": 0
}
  • floatscaled_floatinteger 都有效;若字段是 keyword 会直接抛异常。
  • 内部使用 double 累加,极端精度场景(金融分厘)需自行写 scriptBigDecimal,但性能掉 5×。

3 max / min:极值聚合,秒级出结果

"aggs": {
  "max_price": { "max": { "field": "price" } },
  "min_price": { "min": { "field": "price" } }
}
  • 极值聚合走 BKD 树(数值、日期、IP 字段)或 全局序数(keyword),时间复杂度 O(log n),几乎不受数据量级影响。
  • 对日期字段还能直接拿原始字符串:
"max": { "field": "@timestamp" }

返回 "value_as_string": "2025-11-01T12:00:00.000Z",前端可直接渲染。


4 轻量 ≠ 免费:内存与并发控制

  1. fielddata 禁区
    terms 默认用 global ordinalssegment 级别做字典压缩,如果字段是 text 且没开 keyword 多字段,ES 会尝试加载 fielddata,直接把倒排拉成正排,老年代瞬间暴涨。解决:

    • mapping 里一律保留 keyword 子字段;
    • text 做聚合前先用 sub-field
    "terms": { "field": "title.keyword" }
    
  2. 桶爆炸
    高基数字段(如 user_id、ip、trace_id)做 terms 聚合,桶数量 = 唯一值数量,协调节点需要把全部分片桶回传,内存占用 = 桶数 × 32 byte 左右。

    • 打开 execution_hint:map 把聚合下推到数据节点,减少序列化开销;
    • composite 分页,每次 1 万桶流式吐出;
    • 实在需要全量,用 transform 先预聚合到中间索引。
  3. 并发限流
    高并发 dashboard 同时拉十几种聚合,容易把数据节点 CPU 打满。

    • 开启 search.max_buckets(默认 65536)与 search.allow_expensive_queries
    • 对只读集群打开 adaptive_selection,让协调节点把请求优先发往负载低的分片副本。

5 实战:一条 DSL 同时拿“总数、均值、最大、最小、分布”

GET /nginx-access-2025-11-01/_search
{
  "size": 0,
  "query": { "range": { "@timestamp": { "gte": "now-1h" } } },
  "aggs": {
    "total":            { "value_count": { "field": "bytes" } },
    "avg_bytes":        { "avg":         { "field": "bytes" } },
    "max_bytes":        { "max":         { "field": "bytes" } },
    "min_bytes":        { "min":         { "field": "bytes" } },
    "status_code_dist": {
      "terms": { "field": "status", "size": 10 }
    }
  }
}

一次请求返回:

  • 过去 1 小时总请求数
  • 平均、最大、最小响应字节
  • 状态码分布
    前端 Grafana 可以直接映射到 SingleStat 与 PieChart,无需二次加工。

6 小结

terms、avg、max、min 四个聚合覆盖了 80% 的线上统计需求:

  • terms 负责“分组”,注意 shard_size 与桶爆炸;
  • avg/max/min 负责“数值”,依赖 BKD 树,毫秒级;
  • 一律用 keyword 子字段,禁用 fielddata;
  • 高基数用 composite 或 transform 兜底;
  • 一条 DSL 可以把“搜索+多维指标”一次性带回,真正做到“轻量”。

掌握这四个入口,你就拥有了在 Elasticsearch 里“搜完立刻算”的最低成本方案。下一节,我们将在此基础上引入 percentiles、stats、extended_stats,让指标统计再上一个维度。
更多技术文章见公众号: 大城市小农民

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔丹搞IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值