用 go-elasticsearch/v8 做聚合查询必须显式构造 esapi.searchrequest 并传入 index、body、context 三要素,缺一即返回空结果或 400 错误;body 需为 *bytes.reader,index 为非空字符串切片,context 必须带超时;嵌套聚合需防 oom,multisearch 可提升并发效率,中文聚合须统一 analyzer 并优先使用 .keyword 字段。

用 go-elasticsearch/v8 做聚合查询,必须显式构造 esapi.SearchRequest 并传入 Body 字节流,漏掉 Index、Body 或 Context 中任意一个,ES 就会返回空结果或 400 错误——不是配置问题,是请求本身不合法。
esapi.SearchRequest 的三个必填字段不能省
很多人写完 JSON 聚合体就直接调 .Do(),结果 res.Hits.Total.Value 是 0,res.Aggregations 是 nil。这不是 ES 没数据,而是请求压根没发成功。
-
Index:必须是非空字符串切片,比如[]string{"logs"};传"*"或空切片在生产环境会被拒绝 -
Body:必须是*bytes.Reader,常见错误是json.Marshal()后没包bytes.NewReader(),导致 HTTP body 为空,ES 返回400 Bad Request -
Context:必须带超时,例如context.WithTimeout(ctx, 5*time.Second);不设的话,goroutine 可能卡死在连接池里,日志里看不到错误,只有 CPU 占用缓慢爬升
terms + sub-aggs 嵌套聚合容易 OOM
对数组字段(如 tags、actors)做两层 terms 聚合时,ES 会在内存中构建全量桶树。10 个标签 × 每个标签再分 10 个子桶,实际生成 100 个桶;20 万文档就能轻松撑爆 4GB JVM heap。
- 避免无限制嵌套:内层
terms加"collect_mode": "breadth_first",让 ES 先收顶层 top N,再对这 N 个桶分别查子聚合 - 加
"execution_hint": "map"强制用哈希而非全局排序,降低内存压力(适用于字段基数不高场景) - 生产环境务必设
"size": 10,永远不要留空;ES 默认 size=10,但嵌套时默认行为可能触发全量计算
MultiSearch 拆分聚合提升响应速度
单个请求里塞 5 个独立的 terms 聚合(比如按 country、port、server.keyword 分别统计),ES 是串行执行的。2.6 亿数据下耗时约 10s;改用 esapi.MsearchRequest 并行发 5 个独立请求,实测降到 3s 左右。
- 每个子请求仍需满足前述三要素:
Index、Body、Context - 注意
Body格式:Msearch 要求每条查询前带一个 header 行(如{"index":"logs"}),再跟一个 query body 行 - 并发数别盲目拉高,建议从 3–5 开始压测;ES 线程池有上限,过多并发反而触发
rejected execution
中文聚合必须统一 analyzer
如果索引 mapping 里 title 字段用了 ik_max_word,但聚合时没指定 "field": "title.keyword" 或漏了 "aggs": {"title_terms": {"terms": {"field": "title.keyword"}},ES 会默认走 text 字段的分词结果——“人工智能”被拆成“人工”“智能”,聚合出来的桶名就是碎片词,完全不可读。
- 聚合一律用
.keyword后缀字段,除非你明确需要分词后统计(极少见) - 若真要对中文分词结果聚合,必须在
terms里加"collect_mode": "breadth_first"和"execution_hint": "map",否则极易 OOM - 验证方式:用 Kibana Dev Tools 执行相同聚合,对比返回的
key是否符合预期
最常被忽略的一点:聚合结果里的 key 是原始值(如 "北京"),但如果你在 Go 里用 json.Unmarshal 解析 res.Aggregations 到结构体,字段名必须严格匹配响应中的 key 名,且类型一致——int64 写成 int 或 string 都会导致解析失败,而错误静默吞掉,Aggregations 字段为 nil。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











