Elasticsearch如何高效实现三层条件聚合 优化API响应速度
Elasticsearch三层层级聚合性能优化方案
问题背景
需要实现三层维度的层级计数统计,统计逻辑为:
- 统计全量文档中各
topic的计数 - 统计每个
topic下各bucket的计数 - 统计每个
bucket下各key的计数
当前环境基础信息: - 索引总大小141.7MB,总文档量8000~10000条
topic、bucket、key三个字段的独立取值量级均为1000~2000- 单条文档包含100~200组不同的topic、bucket、key取值,不同文档取值存在差异
当前使用三层嵌套terms聚合实现,API场景下响应耗时3~4秒,无法满足低延迟要求,原查询语句如下:
GET index/doc/_search { "aggs":{ "topic_aggs": { "terms": { "field": "topic.keyword", "size":10000 }, "aggs" : { "bucket_aggs" : { "terms": { "field": "bucket.keyword", "size":10000 }, "aggs" : { "key_aggs": { "terms": { "field": "key.keyword", "size":10000 } } } } } } } }
慢查询核心原因
- 每层terms聚合设置的
size:10000远大于字段实际1000~2000的独立取值量,每一层聚合都会触发不必要的优先级队列排序开销,嵌套层级下开销会逐层放大 - 未添加
size:0参数,查询默认返回Top10原始文档,额外产生文档抓取、排序、序列化传输开销 - 参与聚合的keyword字段未做预优化,首次查询时需要临时构建全局序数结构,增加查询阶段耗时
优化方案(按落地优先级排序)
所有优化落地后,查询耗时可降至100ms以内。
- 匹配字段实际基数调整聚合size参数
三个字段的独立取值上限为2000,将每层terms的size从10000调整为3000(预留后续取值增长空间),直接砍掉80%的无效排序计算和内存开销。 - 关闭原始文档返回
纯聚合统计场景不需要返回原始文档内容,在查询最外层添加"size": 0参数,跳过文档抓取、排序、序列化全流程的无效开销。 - 为聚合字段开启全局序数预加载
keyword字段做terms聚合依赖全局序数数据结构,默认在查询首次触发时才构建。对于固定参与聚合的字段,在mapping中开启eager_global_ordinals配置,索引刷新时提前构建好全局序数,查询时直接复用,消除查询阶段的结构构建开销。
对应mapping配置:
如果三个字段本身就是独立的keyword类型,直接给对应字段加PUT index/_mapping/doc { "properties": { "topic": { "type": "text", "fields": { "keyword": { "type": "keyword", "eager_global_ordinals": true } } }, "bucket": { "type": "text", "fields": { "keyword": { "type": "keyword", "eager_global_ordinals": true } } }, "key": { "type": "text", "fields": { "keyword": { "type": "keyword", "eager_global_ordinals": true } } } } }eager_global_ordinals: true配置即可。 - 添加低基数场景适配的执行hint
对于基数在3000以内的keyword字段terms聚合,添加"execution_hint": "map"参数,直接使用内存哈希表做计数,跳过默认的全局序数遍历逻辑,小基数场景下性能提升明显。
优化后完整查询语句
GET index/doc/_search { "size": 0, "aggs":{ "topic_aggs": { "terms": { "field": "topic.keyword", "size": 3000, "execution_hint": "map" }, "aggs" : { "bucket_aggs" : { "terms": { "field": "bucket.keyword", "size": 3000, "execution_hint": "map" }, "aggs" : { "key_aggs": { "terms": { "field": "key.keyword", "size": 3000, "execution_hint": "map" } } } } } } } }
注意:如果后续单字段独立取值超过5000,移除
execution_hint: map配置即可,map执行模式在高基数场景下内存开销会高于默认的全局序数模式。
可选进阶优化(数据更新频率低场景)
如果索引数据为T+1更新或者小时级更新的静态数据,不需要实时聚合,可以通过定时任务提前计算好三层聚合结果,存入单独的结果索引,API直接查询预计算结果,响应耗时可降至10ms级别。
内容的提问来源于stack exchange,提问作者Robin Rawat
相关产品推荐
相关产品推荐

