📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

适用版本: 6.8-7.15

1. 错误异常的基本描述 #

Expected [START_OBJECT] under [fieldName], but got a [token] in [aggregationName] 是 Elasticsearch 在解析聚合(Aggregation)DSL 时抛出的 ParsingException。该异常表示:解析器期望在指定字段 fieldName 下读取到一个 JSON 对象(START_OBJECT,即 {),但实际读到的却是其他类型的 token(如字符串 VALUE_STRING、数字 VALUE_NUMBER、数组 START_ARRAY 等)。

常见现象 #

  • 执行包含聚合的搜索请求时,Elasticsearch 直接返回 400 Bad Request,响应体中包含上述异常信息。
  • Kibana 或自定义 Dashboard 在加载可视化时报错,无法正常展示图表。
  • 客户端(如 Java High Level REST Client、Python elasticsearch-py 等)抛出异常,堆栈中可见 ParsingExceptionElasticsearchStatusException
  • 在 Elasticsearch 服务端日志(elasticsearch.log)中可以看到类似的错误信息,并附带解析位置的行列号。

典型报错示例 #

{
  "error": {
    "root_cause": [
      {
        "type": "parsing_exception",
        "reason": "Expected [START_OBJECT] under [terms], but got [VALUE_STRING] in [my_aggregation]"
      }
    ],
    "type": "parsing_exception",
    "reason": "Expected [START_OBJECT] under [terms], but got [VALUE_STRING] in [my_aggregation]"
  },
  "status": 400
}

2. 为什么会发生这个错误 #

该异常发生在 Elasticsearch 的聚合 DSL 解析阶段。聚合定义的每个字段对 JSON 结构有严格要求,当实际传入的 JSON 与解析器预期的结构不匹配时就会触发此错误。

常见原因包括:

  • 聚合配置缺少对象层:在某个本应是对象的字段下直接写了字符串或数字。例如 terms 聚合的 field 应该嵌套在对象中,却写成了顶层键值对。
  • 误将对象写成字符串或数组:例如把 range 聚合的 ranges 字段写成了字符串,而不是数组中的对象。
  • 模板渲染错误:使用模板引擎(如 Mustache、Jinja2)动态生成 DSL 时,对象被错误展开为标量值,导致 JSON 结构改变。
  • 客户端序列化问题:在 Java/Python 等客户端中手动拼装 JSON 时,MapObject 类型被错误地序列化成了字符串。
  • 复制粘贴导致结构丢失:从文档或示例复制 DSL 时,遗漏了花括号 {},导致嵌套结构被破坏。

3. 如何排查这个异常 #

建议按以下步骤定位问题:

  1. 读取完整错误信息:从异常信息中提取 aggregationName(聚合名称)和 fieldName(字段名称),这是定位问题的关键线索。
  2. 检查 DSL 结构:根据 aggregationName 找到对应聚合定义,检查 fieldName 下的 JSON 是否以 { 正确开始和结束。
  3. 打印最终 JSON:如果请求是程序拼装的,务必在发送前打印最终序列化后的 JSON 字符串,而不是只查看中间对象的状态。很多时候对象在序列化阶段才发生结构变化。
  4. 使用 JSON 校验工具:将生成的 DSL 粘贴到 JSON 校验工具(如 jsonlint)中,确认 JSON 格式合法且结构符合预期。
  5. 对照官方文档:参考 Elasticsearch 官方聚合文档,确认当前聚合类型的 DSL 结构是否正确。

排查时需要注意的问题 #

  • 不要只看异常的表面信息,务必结合完整的 DSL 上下文一起分析。
  • 如果聚合是动态生成的(如从配置文件读取再拼装),需要检查配置文件的格式是否正确。
  • 注意区分 START_OBJECT{)、START_ARRAY[)、VALUE_STRING(字符串)、VALUE_NUMBER(数字)等不同 token 类型的含义。

4. 如何解决这个错误 #

常用修复思路 #

  • 补上缺失的对象层:在报错的 fieldName 下补上 {},确保解析器能读到 START_OBJECT
  • 修正字段类型:将误写为字符串、数字或数组的字段,改为当前聚合类型要求的对象结构。
  • 检查模板渲染逻辑:如果使用了模板引擎,确认对象在渲染后仍然保持正确的 JSON 结构,必要时在渲染前后分别打印输出进行比对。
  • 增加 JSON 校验:在聚合 DSL 构造逻辑中,增加序列化后的 JSON 校验步骤,在发送到 Elasticsearch 之前就发现结构问题。

示例:错误与正确写法对比 #

错误示例terms 聚合下缺少对象层:

{
  "aggs": {
    "my_aggs": {
      "terms": "status"   // 错误:terms 的值应该是对象,不是字符串
    }
  }
}

正确示例

{
  "aggs": {
    "my_aggs": {
      "terms": {
        "field": "status"
      }
    }
  }
}

错误示例range 聚合的 ranges 写成字符串:

{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": "[{\"to\": 100}, {\"from\": 100, \"to\": 200}]"  // 错误:这是字符串,不是数组
      }
    }
  }
}

正确示例

{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100 },
          { "from": 100, "to": 200 }
        ]
      }
    }
  }
}

后续注意事项与推荐建议 #

  • 在开发阶段使用 explain=true 参数可以帮助理解查询/聚合的执行细节,但对聚合 DSL 错误本身无直接帮助,重点还是结构校验。
  • 建议在 CI 流程中加入 DSL 合法性检查,避免不合法的聚合配置被部署到生产环境。
  • 对于复杂的动态聚合场景,考虑使用 DSL 构建库(如 Java 客户端的 AggregationBuilders)而非手动拼接字符串,可以大幅减少结构错误。

借助 INFINI 产品提升排障效率 #

  • INFINI Console 可以查看集群的搜索请求日志、聚合执行情况和错误趋势,帮助快速判断是 DSL 结构问题还是数据问题。
  • INFINI Gateway 部署在 Elasticsearch 前面,可以拦截并记录所有搜索请求和响应,异常 DSL 可以被完整保存下来用于后续分析,同时支持对不合法请求进行提前拦截。

5. 小结 #

Expected [START_OBJECT] under [fieldName], but got a [token] in [aggregationName] 是一个结构性的 DSL 解析错误,通常可以依据 aggregationName -> fieldName -> token 三个线索直接定位到问题所在行。修复的核心是确保聚合定义中每个字段的 JSON 类型与 Elasticsearch 的预期一致。通过打印最终 JSON、使用校验工具以及在客户端侧增加结构检查,可以有效避免此类问题的重复发生。

相关错误 #

附:日志上下文 #

下面保留当前页面中的源码或日志片段,便于继续结合异常调用栈定位问题:

} else {
    throw new ParsingException(parser.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + "] under ["
        + fieldName + "], but got a [" + token + "] in [" + aggregationName + "]");
}