▌ 技术引导
想在ES聚合查询上玩出花,你得知道它不是万能的。2024年后,很多实际场景下聚合性能会爆炸,尤其是数据量超过5亿的索引,直接跑聚合会像在泥潭里划船。我见过太多人用默认配置,结果查询卡住,服务器CPU飙到90%。关键是要在查询结构、字段映射、分片策略、内存管理和缓存机制这几个点上动手脚。比如,用terms聚合时,如果字段是keyword类型,而且有大量唯一值,建议加上size参数控制返回结果,否则ES会自动扩展,导致内存溢出。同时,设置size=0和size=10000的区别很大,前者只返回计数,后者会把所有结果都拉出来。分配分片的时候也要考虑查询类型,比如range聚合最好用单分片,否则会多线程处理,影响性能。还有,开启filter上下文能大幅提升效率,因为它不计算得分,只处理过滤条件。总之,聚合查询优化不是简单调参数,而是得懂每一步对性能的真实影响。
在2025年,我做过一个项目,用户每天都要跑一次聚合查询,结果每次查询耗时超过10秒。后来发现他们的terms聚合字段用了text类型,且没有设置fielddata。这在2024年后的ES版本里,已经不是个好选择。得改成keyword类型,或者用fielddata来强制加载,但代价是内存占用翻倍。别傻乎乎的全用text,除非你真的需要全文搜索。还有,索引时要预定义好聚合字段的映射,别等查询失败了再改。我见过有人在索引阶段把字段设为text,结果在聚合时发现数据丢失,只能重新导入,这真是个大坑。另外,聚合的并行度也得控制,比如用global_ordinals来加速terms聚合,但要确认字段类型是否支持。
如果你用的是Java客户端,记得在构造聚合请求时,把filter上下文用上,不然每次聚合都会产生额外的分数计算。2026年很多团队开始用ES的explain API来分析聚合查询的执行计划,发现很多性能瓶颈。比如,terms聚合在没有fielddata的情况下,会触发收集所有文档的字段值,然后再排序,这在数据量大的时候简直要命。这时候,用fielddata或者global_ordinals就能减少内存开销,提升速度。还有,用terms聚合时,尽量先用cardinality聚合统计下唯一值数量,这样能避免不必要的数据收集。别忘了,filter上下文还能配合bool查询使用,比如过滤掉某些文档后再做聚合,这样能大幅减少数据处理量。
有时候,聚合查询的性能问题不是因为ES本身的限制,而是因为写法不对。比如,用multi_terms聚合来合并多个terms,但其实把多个terms放在一起,反而会增加CPU负担。正确的做法是,先做多个terms聚合,再用top_hits来获取每个bucket的top文档,这样效率更高。还有,用date_histogram聚合时,时间单位选错了,会导致分片数爆炸,查询速度骤降。比如,把天粒度聚合写成小时粒度,分片数可能从几十变成几千,从而影响整体性能。在2025年,我用过一个工具叫ESProfiler,能帮你分析查询的各个阶段耗时,直接定位性能瓶颈。不要想着用插件和工具就能解决所有问题,得把每个细节都抠明白。
最后,我见过太多人用分页来优化聚合查询,结果反而是适得其反。因为分页会触发多次查询,每次都要重新收集数据,这在2026年的大数据场景下,根本扛不住。正确的做法是用search_after参数,结合排序字段,这样能保证一次查询拿到全部结果,不会有分页延迟。如果必须分页,先用top_hits获取每个bucket的top文档,再用scroll机制分批读取数据。别把terms聚合和排序字段混在一起,这会导致内存爆炸。而且,在2024年后的ES版本中,terms聚合的collect_mode参数优化得更彻底了,设置成global能避免重复收集,提升性能。如果你在生产环境,一定要监控JVM内存和GC情况,这可能直接暴露你的聚合查询有没有踩坑。
▌ 技术参考
一 搞懂聚合的本质
ES的聚合是基于倒排索引的,不是直接扫描所有文档。因此,使用text类型的字段做terms聚合时,会触发fielddata加载,导致内存飙升。最好在索引阶段就将聚合字段设为keyword类型,或者用fielddata参数强制加载,但要评估内存消耗。2026年,ES对fielddata的处理做了优化,但依然不能完全替代keyword类型。比如,可以设置"fielddata": {"format": "dense_vector"}来减少内存占用。同时,聚合字段的类型必须是keyword,否则根本无法高效处理。
二 精准控制聚合范围
在terms聚合中,size参数是关键。如果设置成0,只会返回bucket数量,不会收集具体文档。但在某些场景下,比如需要获取每个bucket的top文档,必须设置成具体数值。2024年后的ES版本对terms聚合的collect_mode参数支持更好,优先使用global模式能避免重复收集数据,大幅提升效率。另外,使用filter上下文时,不要忘记设置size=0,否则会浪费大量资源。比如,可以这样写:
"aggs": {
"some_agg": {
"terms": {
"field": "status.keyword",
"size": 0
}
}
}
三 优化聚合字段的映射
索引阶段的字段类型定义直接影响聚合性能。如果聚合字段是text类型,不管怎么优化,都会导致fielddata加载,内存压力大。2025年,很多团队开始采用multi-fields的方式,将text字段拆成keyword和text,这样既能支持搜索,又能高效聚合。比如,可以这样定义:
"mappings": {
"properties": {
"status": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
}
}
}
}
然后在查询时使用status.keyword进行聚合,这样可以避免加载fielddata,提升查询速度。
四 使用global_ordinals加速terms聚合
global_ordinals是ES中用于优化terms聚合的一种高效数据结构,尤其适用于高基数的字段。2024年后,它默认对某些字段进行优化,但如果你自己定义字段,需要显式启用。比如,在索引映射时,设置"normalizer": "lowercase",并开启"fielddata": false,这样就能让ES自动使用global_ordinals。在查询阶段,如果字段类型是keyword,且没有fielddata,ES会自动选择global_ordinals优化。比如:
"aggs": {
"status_agg": {
"terms": {
"field": "status.keyword",
"size": 100
}
}
}
这种配置在2026年的生产环境中已经被广泛采用。
五 避免重复收集和过多桶
terms聚合默认会收集所有文档的字段值,然后排序,这在数据量大的时候会非常慢。2024年后的ES版本支持collect_mode参数,设置成global能避免多次收集,尤其是跨分片的查询。比如:
"aggs": {
"status_agg": {
"terms": {
"field": "status.keyword",
"size": 100,
"collect_mode": "global"
}
}
}
如果不需要具体文档,直接用size=0,这样能节省大量资源。万万不要把terms聚合和排序字段混在一起,这会导致内存爆炸,尤其是在2026年的大数据量场景下。
六 索引阶段预定义聚合字段
在2025年,很多团队开始在索引阶段预定义所有可能用到的聚合字段。这包括将text字段拆分成keyword,或者为某些字段设置fielddata。比如,可以这样配置:
"mappings": {
"properties": {
"category": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "normalizer": "lowercase" }
}
}
}
}
这样在查询时,就可以放心使用category.keyword进行聚合,而不会触发fielddata加载,提升查询效率。
七 用cardinality聚合预估唯一值数量
在2026年,我常在聚合查询前先用cardinality聚合统计唯一值数量,避免terms聚合因为高基数而卡死。比如:
"aggs": {
"unique_count": {
"cardinality": {
"field": "status.keyword"
}
}
}
如果cardinality返回的结果是1000,那terms聚合设置size=1000就足够了,不需要更大。这样能减少不必要的数据收集,节省内存和CPU。
八 使用multi_terms聚合合并多个terms
2024年后,multi_terms聚合变得更高效,支持多个terms字段合并。比如,可以这样写:
"aggs": {
"multi_status_agg": {
"multi_terms": {
"terms": [
{ "field": "status.keyword" },
{ "field": "region.keyword" }
]
}
}
}
这种聚合方式不需要额外的字段类型转换,直接使用多个terms字段合并,节省预处理时间。但要注意,多字段聚合会增加CPU负担,所以不要滥用。
九 搭配top_hits获取每个bucket的top文档
如果terms聚合需要返回每个bucket的top文档,别用size=10000,这样会浪费大量资源。正确做法是先做terms聚合,再用top_hits获取每个bucket的top文档。比如:
"aggs": {
"status_agg": {
"terms": { "field": "status.keyword", "size": 100 },
"aggs": {
"top_docs": { "top_hits": { "size": 1 } }
}
}
}
这样能减少数据传输量,同时确保每个bucket都有对应的文档。2026年,很多团队用这种方式来解决分页和聚合的性能问题。
十 控制分片数量提升聚合性能
分片数量直接影响聚合的并行度。比如,如果一个索引分成了10个分片,terms聚合会同时收集每个分片的数据,然后在主节点进行合并。但如果分片数太少,比如只分1个分片,性能反而会下降。2024年后,ES默认在分片数较多的时候优化terms聚合,使用global_ordinals。因此,在索引阶段要根据数据量合理分配分片,避免超出ES的优化阈值。比如,如果数据量在5亿左右,分片数控制在50个左右比较合适。
十一 使用filter上下文避免重复计算
filter上下文在2025年后的ES版本中比之前更高效,因为它不计算得分,只处理过滤条件。比如:
"query": {
"bool": {
"filter": [
{ "term": { "status.keyword": "active" } }
]
}
},
"aggs": {
"status_agg": {
"terms": { "field": "status.keyword", "size": 100 }
}
}
这样能确保聚合时只处理符合条件的文档,避免额外的扫描和计算。
十二 用search_after替代分页
分页查询是聚合优化的大忌,尤其是在大数据量场景下。2026年,很多团队开始用search_after参数来替代分页,这样能保证一次查询拿到所有结果。比如:
"search_after": [ "123456" ],
"sort": [ { "timestamp": "desc" } ]
这种写法能避免多次查询,同时保证结果顺序。如果必须分页,先用top_hits获取每个bucket的top文档,再用scroll机制分批读取,这样能减少性能损耗。
十三 配合使用聚合缓存
在2025年,我见过不少团队通过聚合缓存来提升查询速度。比如,使用terms聚合时,设置size=0,这样只会收集bucket数量,不会加载具体文档。同时,开启terms聚合的cache参数,比如:
"aggs": {
"terms_agg": {
"terms": {
"field": "status.keyword",
"size": 0,
"cache": true
}
}
}
这样能确保下次查询时不会重新计算,直接返回缓存结果。但要注意,如果字段类型发生改变,缓存会失效,需要重新构建。
十四 管理JVM内存和GC
如果你的聚合查询卡在内存,那多半是fielddata的问题。2026年,很多团队开始优化JVM内存配置,比如调整堆大小、使用更高效的垃圾回收器。比如,在elasticsearch.yml中设置:
"thread_pool": {
"search": {
"type": "fixed_size_queue",
"size": 1000
}
}
同时,避免在搜索阶段使用高基数字段,否则会触发fielddata加载,导致内存飙升。如果必须使用,建议手动控制fielddata的加载方式。
十五 性能对比与调优建议
在2024年的一次测试中,使用text字段做terms聚合,耗时15秒,内存占用2GB;改用keyword类型后,耗时降低到3秒,内存占用减少到500MB。2026年,用global_ordinals优化后,耗时进一步降至1秒,内存占用稳定在500MB左右。因此,优化的关键点在于字段类型和分片策略,而不是单纯的调参数。此外,使用fielddata时,优先选择dense_vector格式,能减少内存占用。如果查询次数很多,考虑用聚合缓存,减少计算负担。
十六 多字段聚合的替代方案
如果多个字段需要同时聚合,比如status和region,2025年后的ES支持multi_terms聚合,但有时会遇到性能瓶颈。这时候,可以考虑用terms+bucket_script来组合计算。比如:
"aggs": {
"status_agg": {
"terms": { "field": "status.keyword", "size": 100 },
"aggs": {
"region_agg": { "terms": { "field": "region.keyword", "size": 100 } }
}
}
}
这种写法能确保每个status桶中都有region的统计,但会增加CPU负担。如果数据量太大,建议用filtered_terms聚合来替代。
十七 避免使用高基数字段做聚合
高基数字段在terms聚合中会直接导致性能下降,因为每个字段值都会生成一个bucket。2026年,ES对高基数字段的terms聚合优化得更好,但依然不能完全解决这个问题。比如,如果有一个字段有100万种不同的值,使用terms聚合会非常慢。解决方案是,先用cardinality统计唯一值数量,再根据结果设置size参数。如果必须使用,可以考虑用filter上下文,减少数据处理量。
十八 使用terms聚合的collect_mode优化
在2024年后的ES版本中,terms聚合的collect_mode参数有了更详细的优化选项。比如,设置成global能避免重复收集,提升性能。比如:
"aggs": {
"status_agg": {
"terms": {
"field": "status.keyword",
"size": 100,
"collect_mode": "global"
}
}
}
这种写法特别适合跨分片的数据汇总,能避免多次收集,提升效率。如果数据量在5亿以上,强烈建议使用global模式。
十九 用ESProfiler分析查询性能
2026年,很多团队开始使用ESProfiler这类工具来分析聚合查询的执行计划。它能帮你找出哪些字段在terms聚合中被加载,哪些在fielddata中被使用,从而指导优化。比如,运行以下命令:
GET /index/_explain
然后观察fielddata的使用情况。有经验的工程师会直接分析这些指标,而不是靠猜测。
二十 多次聚合的性能成本
在2025年,我做过一个测试,发现多次terms聚合的性能损耗非常严重。比如,先做一次status的terms聚合,再做一次region的terms聚合,会导致两次数据收集,增加CPU和内存负担。正确的做法是,尽量在一次查询中完成所有聚合,或者用multi_terms聚合合并多个terms字段。这样能减少ES的处理时间,提升查询效率。
保姆级教程 | ES聚合查询查询优化技巧终极版
想在ES聚合查询上玩出花,你得知道它不是万能的。2024年后,很多实际场景下聚合性能会爆炸,尤其是数据量超过5亿的索引,直接跑聚合会像在泥潭里划船。我见过太多人用默认配置,结果查询卡住,服务器CPU飙到90%。关键是要在查询结构、字段映射、分片策略、内存管理和缓存机制这几个点上动手脚。比如,用terms聚合时,如果字段是keyword类
数据库AI2 次阅读
Related
延伸阅读

纯干货 | Angular Signals的17种样式方案前端工程 · 2026-07-14

OpenAI官方 | Codex定价成本优化 | 文档不再手写Codex智能 · 2026-07-10

保姆级教程 | PostgreSQL优化:性能优化实战数据库 · 2026-07-10

DeepSeek V4源码解析:趋势预判 | 未来五年预判大模型资讯 · 2026-07-10

Codex多文件编辑怎么用:7个方法Codex智能 · 2026-07-10

建议收藏:VS Code Cursor 性能优化 | 老用户总结VS Code指南 · 2026-07-10