适用版本: 6.8-7.15
1. 错误异常的基本描述 #
Aggregation definition for [aggregationName] starts with a [START_OBJECT]; expected a [START_OBJECT] 是 Elasticsearch 在解析聚合(Aggregation)DSL 时抛出的语法异常。该错误表明聚合定义的 JSON 结构不符合 Elasticsearch 的预期格式,具体是聚合名称后面紧跟的内容不是一个合法的 JSON 对象({ })。
常见现象 #
- 搜索请求返回 HTTP
400 Bad Request,响应体中包含parse_exception或x_content_parse_exception。 - 应用侧表现为搜索接口调用失败,聚合结果缺失,或整个查询请求被拒绝。
- 在 Elasticsearch 服务端日志中可检索到如下关键字:
Aggregation definition for [...] starts with a [parse_exceptionx_content_parse_exception
典型报错与异常栈 #
实际报错信息通常如下:
{
"error": {
"root_cause": [
{
"type": "parse_exception",
"reason": "Aggregation definition for [my_agg] starts with a [START_ARRAY]; expected a [START_OBJECT]"
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"failed_shards": [
{
"reason": {
"type": "parse_exception",
"reason": "Aggregation definition for [my_agg] starts with a [START_ARRAY]; expected a [START_OBJECT]"
}
}
]
},
"status": 400
}
2. 为什么会发生这个错误 #
Elasticsearch 的聚合 DSL 有严格的 JSON 结构要求:每个聚合名称后面必须跟一个 JSON 对象({ }),该对象内部定义聚合的类型和具体参数。当解析器在读取聚合名称后遇到的不是 {,就会抛出此异常。
常见原因 #
- 聚合名称后缺少对象包裹:直接把聚合类型或参数写在聚合名称后面,而没有用
{ }包裹。 - 错误使用数组
[]代替对象{}:在聚合定义中误用方括号,例如"my_agg": [ { "terms": ... } ]。 - DSL 拼接或模板渲染错误:通过字符串拼接或模板引擎生成 DSL 时,结构被破坏,导致聚合定义格式非法。
- 嵌套聚合结构错误:在定义嵌套聚合(sub-aggregations)时,子聚合没有正确嵌套在
aggs对象内。 - JSON 结构层级错误:聚合类型字段(如
terms、avg)没有作为对象的键,而是被直接放在了错误的位置。
3. 如何排查这个异常 #
建议按以下步骤逐一排查:
3.1 获取完整请求体 #
- 从应用日志或 Elasticsearch 慢查询日志中抓取完整的搜索请求体。
- 如果是通过代码拼接 DSL,建议在发送前打印最终生成的 JSON 字符串。
- 使用 JSON 格式化工具(如
jq或在 IDE 中格式化)检查 DSL 结构是否合法。
3.2 定位问题聚合 #
- 如果请求中包含多个聚合,逐个移除聚合定义,直到错误消失,从而定位具体是哪个聚合有问题。
- 重点检查错误提示中的聚合名称(
aggregationName),确认其定义结构。 - 对比正确聚合 DSL 的官方示例,检查结构差异。
3.3 使用 Kibana Dev Tools 验证 #
在 Kibana Dev Tools 或 CURL 中直接发送简化后的 DSL,逐步添加聚合子句,确认每一步是否合法:
# 基础查询,先不加聚合
GET /my_index/_search
{
"size": 0
}
# 逐步添加聚合,验证每一步
GET /my_index/_search
{
"size": 0,
"aggs": {
"my_agg": {
"terms": { "field": "category.keyword" }
}
}
}
4. 如何解决这个错误 #
4.1 正确的聚合 DSL 结构 #
标准的聚合 DSL 结构如下:
GET /my_index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"aggregation_type": {
"field": "field_name"
}
}
}
}
4.2 常见错误与修复对照 #
错误示例 1:聚合名称后直接跟数组
{
"aggs": {
"my_agg": [
{ "terms": { "field": "category.keyword" } }
]
}
}
修复后:
{
"aggs": {
"my_agg": {
"terms": { "field": "category.keyword" }
}
}
}
错误示例 2:聚合类型不在对象内
{
"aggs": {
"my_agg": "terms",
"field": "category.keyword"
}
}
修复后:
{
"aggs": {
"my_agg": {
"terms": { "field": "category.keyword" }
}
}
}
错误示例 3:嵌套子聚合结构错误
{
"aggs": {
"by_category": {
"terms": { "field": "category.keyword" },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
}
注意:子聚合 aggs 必须嵌套在父聚合的对象内部,与上述结构一致才是正确的。
4.3 通过 INFINI Gateway 辅助排查 #
如果请求经过 INFINI Gateway,可以在请求日志中直接查看发送给 Elasticsearch 的原始 DSL,快速确认是否存在格式问题,无需登录服务器抓取日志。
5. 如何预防此类错误 #
5.1 开发阶段 #
- 使用强类型的客户端库(如 Java High Level REST Client)构建 DSL,避免手动拼接 JSON 字符串。
- 在单元测试中对生成 DSL 的方法进行验证,确保输出是合法 JSON 且结构正确。
- 参考 Elasticsearch 官方聚合文档 编写聚合查询,避免凭记忆猜测结构。
5.2 运行时防护 #
- 在
INFINI Console 中配置对
400错误和parse_exception的告警,及时发现 DSL 问题。 - 通过 INFINI Gateway 对异常 DSL 进行请求拦截和日志记录,防止非法请求进入集群。
- 建立搜索 DSL 的代码审查机制,聚合查询的变更必须经过评审后上线。
5.3 最佳实践 #
- 聚合名称使用有意义的英文命名,避免使用特殊字符(方括号
[]、尖括号<>等)。 - 复杂聚合查询分步骤构建,每添加一层嵌套就验证一次结果。
- 对生产环境的聚合查询设置合理的
size上限,避免结果过大导致额外问题。
6. 小结 #
Aggregation definition starts with a [...] 错误的根本原因是聚合 DSL 的 JSON 结构不符合 Elasticsearch 的解析要求,最常见的问题是聚合名称后没有正确使用对象 {} 包裹聚合类型定义。修复方法是仔细检查聚合名称后的结构,确保其为一个合法的 JSON 对象,且内部包含正确的聚合类型字段。
通过规范的 DSL 构建方式、充分的本地验证以及 INFINI Console 和 INFINI Gateway 的辅助观测,可以在开发和运行时有效避免此类问题。
相关错误 #
- unregistered-aggregation-aggregationname:未注册的聚合
- unsupported-aggregation-type:不支持的聚合类型
- valuessource-type-valuessource-tostring-is-not-supported-for-aggregation:ValueSource类型不支持聚合
- unsupported-operation-parsed-aggregations-are-null:聚合解析为空
- aggregation-execution-exception:聚合执行异常
附:源码上下文 #
以下为 Elasticsearch 源码中触发此异常的代码片段,帮助理解底层校验逻辑:
// org.elasticsearch.search.aggregations.AggregatorParsers
if (token != XContentParser.Token.START_OBJECT) {
throw new ParsingException(
parser.getTokenLocation(),
"Aggregation definition for [" + aggregationName +
"] starts with a [" + token + "]; expected a [" +
XContentParser.Token.START_OBJECT + "]."
);
}





