ES 创建太多 buckets 错误: trying to create too many buckets. must be less than or equal to: [100000] but w

ES 创建太多 buckets 错误: trying to create too many buckets. must be less than or equal to: [100000] but w,第1张

ES 创建太多 buckets 错误: trying to create too many buckets. must be less than or equal to: [100000] but w ES 创建太多 buckets 错误: trying to create too many buckets. must be less than or equal to: [100000] but was [100001].

错误描述:

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 的原因就是我们虽然进行了过滤,但最终数据不可避免的有无效数据参与了分桶。

基于上述的梳理,我大概有以下几个方案

方案:

  1. 为这类查询单独建立一个索引 (维护代价较大)。
  2. 分页
  3. 调小聚合 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 代码层面,只需要进行分页分部查询,最后聚合数据即可。

最终解决思路:

  1. 缩小聚合数据范围 (探寻所有可用的限制条件,能限制尽量限制)
  2. 使用 composite 对聚合数据分页 (es 查询分页在代码层面最终聚合结果)
  3. 调整合适的 size 大小 (如果你的聚合项有冗余数据,可以考虑调小结果 size)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5634759.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存