错误描述:
trying to create too many buckets. must be less than or equal to: [100000] but was [100001]. this limit can be set by changing the [search.max_buckets] cluster level setting.
一般的解决方法 调大 search.max_buckets 的值,在 kibana 中直接执行下列语句:
PUT /_cluster/settings { "persistent": { "search.max_buckets": 20000 } }
如果你的服务器能撑住,或者自身评估直接扩大并无问题,那么本文的阅读就可以到此为止了
但是我对扩张 search.max_buckets 感到担忧怎么办?
我们跟踪一个例子向下探索解决方案。
显然 es 默认设置 10000 的上限是有原因的,这块需要对你的服务器性能有一个评估,考虑你的 es 服务是否能撑得住这种大量的聚合计算,冒然扩大限制可能导致服务的崩溃。
如果预计的 buckets 数量级别过大,就需要结合具体场景分析在查询层面进行优化。
Tips 最终解决思路请直接移步文末
我这里实际遇到的需求场景是:
目前需要对用户在工作流中的自定义字段进行聚合
需要计算出当前工作流任务中 自定义字段选项的分布情况(仅支持单选、多选、多级选择)。
例如 有个自定义字段是 ”地区“ 其中有选项 ”北京“ ”上海“ ,那么就需要算出 填选了 北京 的任务有多少个 上海 的任务有多少个
// 一个最简原型 mapping { "work_flow" : { "mappings" : { "properties" : { "create_time" : { "type" : "date" }, "fields" : { "type" : "nested", "properties" : { "field_id" : { "type" : "long" }, "field_type" : { "type" : "long" }, "field_value" : { "type" : "text" }, "field_value_key" : { "type" : "keyword" } } }, "group_id" : { "type" : "long" }, "task_id" : { "type" : "long" } } } } }
虽然我们需求上限制了 仅支持单选、多选、多级选择,但是自定义字段的 es index 是在一起的,上述需求聚合时免不了要对字段值进行分桶聚合,此时就会将可以开放填写的文本字段也聚合进来,即使这个字段值仅仅只有 1 个任务匹配。当这个工作流中的任务基数足够大的时候就会产生分桶爆炸。
GET /work_flow/_search { "size": 0, "timeout": "5s", "query": { "bool": { "must": [ { "term": { "group_id": 666 } }, { "range": { "create_time": { "from": 1625068800000, "to": 1627487999000 } } } ] } }, "track_total_hits": false, "aggregations": { "fields": { "nested": { "path": "fields" }, "aggregations": { "fields.field_id": { "terms": { "field": "fields.field_id", "size": 2147483647 }, "aggregations": { "fields.field_value_key": { "terms": { "field": "fields.field_value_key", "size": 2147483647 } } } } } } } }
有一个思路是对字段类型进行过滤后进行聚合,这个思路看似是可行的,但是忽略了一个问题,当前索引是以任务为基本单位存储数据的,自定义字段仅仅是附属值,而一个任务可能多个自定义字段都有值,所以这个过滤可以生效,但是效果并不大。而且如果你存了其他自定义字段的空值,这个过滤就完全没有效果了。但是聊胜于无,在查询时加下空串判断
GET /work_flow/_search { "size": 0, "timeout": "5s", "query": { "bool": { "must": [ { "range": { "create_time": { "from": 1635696000000, "to": 1638201599000 } } } ], "filter": [ { "term": { "group_id": 666 } }, { "nested": { "query": { "bool": { "must": [ { "terms": { "fields.field_type": [ 2, 4, 5 ] } } ], "must_not": { "term":{ "fields.field_value_key":"" } } } }, "path": "fields" } } ] } }, "track_total_hits": false, "aggregations": { "fields": { "nested": { "path": "fields" }, "aggregations": { "fields.field_id": { "terms": { "field": "fields.field_id", "size": 2147483647 }, "aggregations": { "fields.field_value_key": { "terms": { "field": "fields.field_value_key", "size": 2147483647 } } } } } } } }
重新整理一下现在的问题:
es 首先根据我们的要求找到了目标工作流,并且过滤剩下了只包含了 单选,多选,多级选择 的任务 但是这些任务数据中同时可能包含其他自定义字段 之后 es 进行聚合统计,这里导致 trying to create too many buckets 的原因就是我们虽然进行了过滤,但最终数据不可避免的有无效数据参与了分桶。
基于上述的梳理,我大概有以下几个方案
方案:
- 为这类查询单独建立一个索引 (维护代价较大)。
- 分页
- 调小聚合 size
这里采用 2、3 结合的方式进行处理 聚合 size 考虑给到 200 ,具体可以根据场景进行调整
GET /work_flow/_search { "size": 0, "timeout": "5s", "query": { "bool": { "must": [ { "range": { "create_time": { "from": 1635696000000, "to": 1638201599000 } } } ], "filter": [ { "term": { "group_id": 666 } }, { "nested": { "query": { "bool": { "must": [ { "terms": { "fields.field_type": [ 2, 4, 5 ] } } ], "must_not": { "term":{ "fields.field_value_key":"" } } } }, "path": "fields" } } ] } }, "track_total_hits": false, "aggregations": { "fields": { "nested": { "path": "fields" }, "aggregations": { "fields.field_id": { "composite": { "size": 5, "sources": [ { "fields": { "terms": { "field": "fields.field_id" } } } ] }, "aggregations": { "fields.field_value_key": { "terms": { "field": "fields.field_value_key", "size": 200 } } } } } } } }
这里对 fieldId 进行了分页,加入了 composite
"composite": { "size": 5, "sources": [ { "fields": { "terms": { "field": "fields.field_id" } } } ] },
返回时会返回 after_key 下次请求时带上
"fields.field_id" : { "after_key" : { "fields" : 185 }
"fields.field_id": { "composite": { "size": 1, "sources": [ { "fields": { "terms": { "field": "fields.field_id" } } } ], "after": {"fields":185} },
这里 DSL 的思路已经搞定了,那么在 Java 代码层面,只需要进行分页分部查询,最后聚合数据即可。
最终解决思路:
- 缩小聚合数据范围 (探寻所有可用的限制条件,能限制尽量限制)
- 使用 composite 对聚合数据分页 (es 查询分页在代码层面最终聚合结果)
- 调整合适的 size 大小 (如果你的聚合项有冗余数据,可以考虑调小结果 size)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)