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

适用版本: 8.3-8.9(geo_grid 查询自 8.3 版本引入)

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

failed to parse [query] query. grid name not provided 是 Elasticsearch 在解析 geo_grid 查询时抛出的 ElasticsearchParseException。该错误表示请求中缺少 grid 字段,即未指定要使用哪种地理网格编码类型,导致 Elasticsearch 无法构建 GeoGridQueryBuilder

geo_grid 查询是 Elasticsearch 8.3 引入的一种基于地理网格的精确查询方式,允许用户通过网格 ID 直接匹配落在特定网格单元内的地理点数据。与 geotile_gridgeohash_grid 聚合不同,geo_grid 查询是一个文档过滤查询,用于判断文档的地理坐标是否落在指定的网格单元内。

常见现象 #

  • 发起 geo_grid 查询时,Elasticsearch 直接返回 400 Bad Request
  • 返回的错误信息类似:
    {
      "error": {
        "root_cause": [
          {
            "type": "parse_exception",
            "reason": "failed to parse [geo_grid] query. grid name not provided"
          }
        ],
        "type": "parse_exception",
        "reason": "failed to parse [geo_grid] query. grid name not provided"
      },
      "status": 400
    }
    
  • 使用 Kibana Dev Tools、cURL 或任何 Elasticsearch 客户端(Java、Python、Go 等)发起请求均会收到相同错误。
  • 如果请求是通过索引模板或搜索模板(Search Template)渲染生成的,则该错误可能在模板渲染阶段之后、查询执行之前被抛出。

典型报错与异常栈 #

在 Elasticsearch 服务端日志(elasticsearch.log)中,该错误对应的完整异常栈通常如下:

[2024-01-15T10:30:45,123][WARN ][o.e.r.a.RestValidateAction ] [node-1] failed to parse [geo_grid] query. grid name not provided
org.elasticsearch.common.ParsingException: failed to parse [geo_grid] query. grid name not provided
    at org.elasticsearch.index.query.GeoGridQueryBuilder.fromXContent(GeoGridQueryBuilder.java:98)
    at org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder(AbstractQueryBuilder.java:337)
    at org.elasticsearch.index.query.BoolQueryBuilder.fromXContent(BoolQueryBuilder.java:156)
    at org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder(AbstractQueryBuilder.java:337)
    at org.elasticsearch.rest.action.search.RestSearchAction.lambda$prepareRequest$0(RestSearchAction.java:102)
    at org.elasticsearch.rest.BaseRestHandler.handleRequest(BaseRestHandler.java:104)
    at org.elasticsearch.rest.RestController.dispatchRequest(RestController.java:335)

注意: 实际行号可能因 Elasticsearch 具体版本而略有差异,但异常类型和错误消息完全一致。

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

从 Elasticsearch 源码(GeoGridQueryBuilder.fromXContent)可以看出,grid 是解析 geo_grid 查询时的第一个必填校验项。只有当 grid 字段被成功解析后,解析器才会继续读取 grid_idfield 等其他参数。

源码核心逻辑如下(简化后):

// GeoGridQueryBuilder.fromXContent 关键片段
String grid = null;
String gridId = null;
String fieldName = null;

// 解析 JSON 字段
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
    if ("grid".equals(fieldName)) {
        grid = parser.text();
    } else if ("grid_id".equals(fieldName)) {
        gridId = parser.text();
    } else if ("field".equals(fieldName)) {
        fieldName = parser.text();
    }
    // ...
}

// 关键校验:grid 必须非空
if (grid == null) {
    throw new ElasticsearchParseException(
        "failed to parse [{}] query. grid name not provided", NAME);
}

常见原因 #

  1. 请求中完全缺少 grid 字段:这是最直接的原因。查询 JSON 里只提供了 fieldgrid_id,但没有 grid

  2. grid 字段位置错误grid 必须位于 geo_grid 查询对象的直接子级,不能嵌套在其他对象里。如果请求是在程序中动态组装的,可能因 JSON 结构拼接错误导致 grid 被放在了错误层级。

  3. 模板渲染漏字段:如果查询是通过 Index Template、Search Template 或外部配置模板生成的,模板中可能遗漏了 grid 字段的定义,或者在变量替换时被意外跳过。

  4. 字段名拼写错误:例如将 grid 误写为 grid_typetypegrid_type_name 等,导致解析器无法识别。

  5. SDK 或客户端封装问题:某些语言客户端如果未正确封装 geo_grid 查询,可能在序列化时丢失 grid 字段。

  6. grid 值为 null 或空字符串:即使 JSON 中出现了 "grid": """grid": null,解析器也会将其视为未提供(因为 parser.text() 返回 null 或空值后校验不通过)。

3. 如何排查和解决这个异常 #

3.1 确认当前 Elasticsearch 版本是否支持 geo_grid 查询 #

geo_grid 查询自 Elasticsearch 8.3 开始引入。如果运行的是 8.3 之前的版本,该查询类型根本不存在,会报 unknown query [geo_grid] 错误。但如果你看到的是 grid name not provided,说明版本是正确的,只是参数不全。

# 检查 ES 版本
curl -s "http://localhost:9200" | jq .version.number

3.2 抓取并审查完整请求体 #

首先需要确认实际发送到 Elasticsearch 的 JSON 请求体是什么。如果是通过应用程序发起的,建议在发起请求前打印最终序列化后的 JSON。

# 如果你用的是 cURL,直接检查你的 JSON 文件
cat request.json | jq .

3.3 使用最小可复现示例验证 #

用以下错误示例复现问题:

curl -X POST "http://localhost:9200/my_index/_search" -H "Content-Type: application/json" -d '
{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid_id": "7/64/42"
    }
  }
}'

返回结果:

{
  "error": {
    "root_cause": [
      {
        "type": "parse_exception",
        "reason": "failed to parse [geo_grid] query. grid name not provided"
      }
    ],
    "type": "parse_exception",
    "reason": "failed to parse [geo_grid] query. grid name not provided"
  },
  "status": 400
}

对比正确示例

curl -X POST "http://localhost:9200/my_index/_search" -H "Content-Type: application/json" -d '
{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid": "geotile",
      "grid_id": "7/64/42"
    }
  }
}'

3.4 检查索引 Mapping 中地理字段的类型 #

geo_grid 查询的 field 必须指向一个 geo_point 类型的字段。如果字段类型不匹配,即使修复了 grid 参数,后续也可能报其他错误。

# 查看索引 mapping
curl -s "http://localhost:9200/my_index/_mapping" | jq '.[].mappings.properties.location'

正确示例输出:

{
  "type": "geo_point"
}

3.5 排查步骤总结 #

  1. 确认版本:确保 Elasticsearch >= 8.3。
  2. 检查请求 JSON:确认 geo_grid 对象下是否存在 grid 字段,且值合法。
  3. 确认字段位置grid 必须是 geo_grid 的直接子字段,而非嵌套在其他对象内。
  4. 确认字段值grid 的值必须是 geotilegeohash,且不为空。
  5. 检查客户端代码:如果是程序生成的请求,在发送前打印完整 JSON 进行核对。
  6. 检查模板:如果使用 Search Template,确认模板定义中包含了 grid 参数。

3.6 使用 Validate API 辅助排查 #

Elasticsearch 提供了 _validate API,可以在不实际执行查询的情况下检查查询 DSL 是否合法:

curl -X POST "http://localhost:9200/my_index/_validate/query" -H "Content-Type: application/json" -d '
{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid_id": "7/64/42"
    }
  }
}' | jq .

返回结果会明确指出错误原因,适合在 CI/CD 或开发阶段用于验证查询 DSL 的合法性。

4. 如何解决这个错误 #

4.1 修复请求体:补充正确的 grid 字段 #

根据业务需要选择以下两种网格类型之一:

方案一:使用 geotile 网格 #

geotile 是基于 Google Maps 瓦片方案的网格编码,网格 ID 格式为 zoom/x/y

{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid": "geotile",
      "grid_id": "7/64/42"
    }
  }
}

对应含义:zoom=7, x=64, y=42 的瓦片网格单元。

方案二:使用 geohash 网格 #

geohash 是基于 Geohash 编码的网格,网格 ID 为 Geohash 字符串。

{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid": "geohash",
      "grid_id": "wtw3"
    }
  }
}

对应含义:Geohash 值为 wtw3 的网格单元。

完整可复现示例(含索引创建和数据写入) #

# 1. 创建索引,定义 geo_point 字段
curl -X PUT "http://localhost:9200/geo_test" -H "Content-Type: application/json" -d '
{
  "mappings": {
    "properties": {
      "location": { "type": "geo_point" },
      "name": { "type": "keyword" }
    }
  }
}'

# 2. 写入测试数据(上海坐标)
curl -X POST "http://localhost:9200/geo_test/_doc" -H "Content-Type: application/json" -d '
{
  "name": "上海东方明珠",
  "location": { "lat": 31.2304, "lon": 121.4737 }
}'

# 3. 使用 geotile 网格查询
curl -X POST "http://localhost:9200/geo_test/_search" -H "Content-Type: application/json" -d '
{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid": "geotile",
      "grid_id": "7/102/56"
    }
  }
}'

# 4. 使用 geohash 网格查询
curl -X POST "http://localhost:9200/geo_test/_search" -H "Content-Type: application/json" -d '
{
  "query": {
    "geo_grid": {
      "field": "location",
      "grid": "geohash",
      "grid_id": "wtw3"
    }
  }
}'

4.2 在应用程序中的修复建议 #

如果是通过代码动态组装查询,建议在组装完成后、发送请求前做一次校验:

# Python 示例
def build_geo_grid_query(field, grid, grid_id):
    """构建 geo_grid 查询,包含必填字段校验"""
    if not grid:
        raise ValueError("geo_grid 查询必须提供 grid 参数,可选值为: geotile, geohash")
    if not grid_id:
        raise ValueError("geo_grid 查询必须提供 grid_id 参数")
    if grid not in ("geotile", "geohash"):
        raise ValueError(f"不支持的 grid 类型: {grid},仅支持: geotile, geohash")

    return {
        "query": {
            "geo_grid": {
                "field": field,
                "grid": grid,
                "grid_id": grid_id
            }
        }
    }
// Java 示例(使用 Elasticsearch Java Client)
Query gridQuery = GeoGridQuery.of(g -> g
    .field("location")
    .grid(GeoGridType.Geotile)   // 明确指定网格类型
    .gridId("7/102/56")
)._toQuery();

4.3 通过 INFINI 产品提升排障效率 #

INFINI Console — 集群查询分析与可视化 #

INFINI Console 可以在不登录服务器的情况下,对 Elasticsearch 集群的查询请求进行全方位观测:

  • 查询 DSL 审查:在 Console 的搜索预览界面直接编写和测试 geo_grid 查询,实时看到解析结果和错误信息,避免在应用程序中反复试错。
  • 索引 Mapping 可视化:一键查看索引的字段类型和 Mapping 配置,确认 geo_point 字段是否正确定义。
  • 错误趋势分析:如果 grid name not provided 错误频繁出现,可以在 Console 的审计日志中定位是哪些客户端 IP、哪些应用账号在发送不合法的请求,从而精准定位问题来源。
  • 查询性能分析:对于已修复的 geo_grid 查询,可以通过 Console 查看其执行耗时和资源消耗,判断是否需要优化网格精度或添加索引。

INFINI Gateway — 请求治理与防护 #

INFINI Gateway 部署在应用与 Elasticsearch 之间,提供强大的请求治理能力:

  • 请求校验与重写:通过 Gateway 的请求过滤规则,可以在请求到达 Elasticsearch 之前就拦截不合法的 geo_grid 查询(缺少 grid 字段),返回更友好的错误提示,避免无效请求打到后端集群。
  • 查询模板管理:Gateway 支持请求模板和变量注入,可以统一管理 geo_grid 查询的格式,避免各业务端自由拼装导致参数缺失。
  • 流量监控与告警:当某个客户端频繁发送包含 grid name not provided 错误的请求时,Gateway 可以自动触发告警,并展示详细的请求来源、频率和完整请求体,大幅缩短排查时间。
  • 缓存加速:对于重复度高的网格查询,Gateway 可以提供查询结果缓存,减少 Elasticsearch 的重复计算压力。

4.4 后续注意事项与预防建议 #

  • 建立查询 DSL 代码审查机制:涉及地理查询的代码变更,必须经过 DSL 合法性验证。
  • 在开发/测试环境使用 Validate API:在 CI 流程中加入查询 DSL 的自动化校验步骤。
  • 统一封装地理查询工具类:避免各业务模块各自拼接 JSON,统一通过工具类生成查询 DSL,并在工具类层面做必填参数校验。
  • 关注版本升级说明:Elasticsearch 每次升级都可能带来查询 DSL 的变化,建议在升级前查阅 Release Notes 中关于 geo_grid 查询的相关说明。

5. 小结 #

failed to parse [query] query. grid name not provided 是一个典型的参数缺失型解析错误,根因是 geo_grid 查询中缺少 grid 字段。修复方法非常直接:在查询 DSL 中补充 grid 参数,取值为 geotilegeohash

排查时,建议按以下顺序进行:

  1. 确认 ES 版本 >= 8.3(geo_grid 查询的最低版本要求)。
  2. 检查请求 JSON 中 geo_grid 对象下是否有 grid 字段,且值合法。
  3. 确认 grid 字段位于正确位置(直接子级),值不为空。
  4. 如果是程序生成的请求,在发送前打印完整 JSON 进行核对。

通过 INFINI ConsoleINFINI Gateway 的组合使用,可以从查询可视化、请求治理、流量监控等多个层面降低此类错误的发生概率,并在问题出现时快速定位根因。

相关错误 #

附:日志上下文(源码片段) #

以下为 GeoGridQueryBuilder.fromXContent 方法中的关键源码片段,展示了 gridgrid_id 的校验顺序:

// GeoGridQueryBuilder.java(Elasticsearch 8.x)
String grid = null;
String gridId = null;
String fieldName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;

// 解析 geo_grid 查询的 JSON 内容
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
    if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
    } else if (token == XContentParser.Token.VALUE_STRING) {
        if ("field".equals(currentFieldName)) {
            fieldName = parser.text();
        } else if ("grid".equals(currentFieldName)) {
            grid = parser.text();   // <-- 读取 grid 字段
        } else if ("grid_id".equals(currentFieldName)) {
            gridId = parser.text(); // <-- 读取 grid_id 字段
        } else if ("query_name".equals(currentFieldName) || "_name".equals(currentFieldName)) {
            queryName = parser.text();
        } else {
            throw new ElasticsearchParseException(
                "failed to parse [{}] query. unknown field [{}]", NAME, currentFieldName);
        }
    } else if (token == XContentParser.Token.VALUE_NUMBER) {
        if ("boost".equals(currentFieldName) || "_boost".equals(currentFieldName)) {
            boost = parser.floatValue();
        }
    }
    // ...
}

// 关键校验 1:grid 必须提供
if (grid == null) {
    throw new ElasticsearchParseException(
        "failed to parse [{}] query. grid name not provided", NAME);
}

// 关键校验 2:grid_id 必须提供
if (gridId == null) {
    throw new ElasticsearchParseException(
        "failed to parse [{}] query. grid id not provided", NAME);
}

// 校验通过,构建 QueryBuilder
GeoGridQueryBuilder builder = new GeoGridQueryBuilder(fieldName);
builder.setGridId(grid, gridId);
builder.queryName(queryName);
builder.boost(boost);
return builder;

从源码可以清晰看到:grid 的校验在 grid_id 之前,因此如果两者都缺失,优先报 grid name not provided