mcpField
E
elasticsearch-query-optimization

elasticsearch-query-optimization

github.com/elastic数据与数据库6021.1k 次安装今天更新
代码仓库

关于 elasticsearch-query-optimization

利用 profile 证据诊断并优化缓慢的 Elasticsearch Query DSL 搜索。它针对现有查询,尽可能保持匹配语义,并在改写后重新测量性能。

主要功能

  1. 01通过 profile 定位主要耗时
  2. 02改写前核对字段映射
  3. 03将无需评分的条件移至 filter
  4. 04替换开销较高的前导通配符
  5. 05比较优化前后的 profile

适用场景

  1. 01排查缓慢的 Query DSL 搜索
  2. 02减少精确匹配条件的评分开销
  3. 03优化由前导通配符主导的搜索

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elastic/agent-skills --skill elasticsearch-query-optimization

安装后在 Claude Code 中输入/elasticsearch-query-optimization确认已加载。

附带文件references/query-optimization-reference.md

详细介绍

Elasticsearch Query DSL 优化

诊断 Query DSL 搜索缓慢的原因,根据 profile 确认主要耗时来源(而不是猜测),在保持匹配语义的前提下重写查询,并在启用 profile 的情况下重新测量。

环境配置

此技能通过 elastic CLI 执行 Elasticsearch 操作。如果未安装 elastic CLI,请告知用户需要安装它。不要猜测凭据、直接调用 HTTP API, 或尝试其他变通方法。

此技能以 HTTP 简写形式引用操作(例如 GET /、GET /_cat/indices、GET /{index}/_mapping、 GET /{index}/_settings/index.mode、POST /_query)。本文档末尾的 操作 表将各简写映射到对应的 elastic CLI 命令——始终使用 CLI,不要直接调用 HTTP API。

**范围:**通过 POST /{index}/_search 执行的 Query DSL 搜索。此技能不会将查询迁移到 ES|QL,而是优化用户当前使用的 bool/match/term/wildcard 结构。

**基本原则:**当 profile 指明了具体子句(例如耗时约 3.8 秒的 WildcardQuery)时,绝不要将“添加分片”或“升级硬件”作为主要解决方案。先修复查询;只有证据表明查询已达到最优时,才考虑基础设施变更。

流程

  1. **确认连接并找到目标索引。**调用 GET /。如果调用失败,请停止操作——不要猜测端点 或凭据。如果用户提供了索引模式(例如 logs-*),通过 GET /_cat/indices 缩小候选范围,并选择查询实际针对的索引或模式。

    **决策:**只有在确认索引后才能继续。**所需数据:**索引名称或模式,以及耗时较长的 Query DSL 请求体(由用户提供或来自已保存的搜索)。

  2. **对耗时较长的查询进行 profile,找出主要耗时来源。**调用 POST /{index}/_search,设置 "profile": true,并保持 用户的查询不变。读取 took,然后检查 profile.shards[].searches[].query——按 time_in_nanos 对子级 collector 排序,并找出耗时最高的项目。

    **决策:**根据 profile 证据对瓶颈分类:

    • must 中与评分子句并列的 TermQuery / PointRangeQuery / MatchNoDocsQuery——精确匹配或 范围过滤器被不必要地计分。可能的修复方法:将它们移到 filter 上下文(步骤 4a)。
    • 带前导 * 的 WildcardQuery(例如 message:*timeout*)——无法使用倒排索引;会逐个文档扫描 词项。可能的修复方法:移除前导通配符(步骤 4b)。
    • 作用于 text 字段的 MatchQuery——这是预期的评分开销;只有在修复 filter 上下文问题后,profile 仍显示它占主导时,才进行优化。
    • aggregation 耗时较高——这与查询调优无关;应分析聚合树(除非用户询问聚合,否则不在范围内)。

    **所需数据:**包含 type、description、time_in_nanos 和 breakdown 的 profile 树(通配符查询尤其要关注 next_doc)。 解释诊断时,逐字引用耗时最高的项目。

  3. **重写前检查字段映射。**调用 GET /{index}/_mapping。对于每个要移动或重写的子句,确认字段类型:

    • 对精确值使用 term / terms / filter——字段必须为 keyword(或其他非分析类型)。在 text 字段上使用 term 是常见错误;如果类型不正确,应指出问题并建议使用正确的子字段(例如 service.keyword)或修改映射——不要悄悄重写。
    • match / match_phrase——目标应为 text 字段(已分析)。
    • wildcard——可用于 keyword 或 wildcard 类型;无论字段类型如何,前导 * 仍会导致扫描。

    **决策:**只有在字段类型已确认的情况下,才能提出重写方案。**所需数据:**查询中引用的每个字段的映射。

  4. 重写查询以消除 profile 显示的瓶颈。

    4a. 将不参与评分的子句从 must 移至 filter

    当对 keyword 的精确匹配 term/terms/range/match 子句(或其他不参与评分的意图)与本应决定相关性的全文 match 一同位于 must 中时:

    • 将精确匹配子句移入 bool.filter(或 filter 数组项)。
    • 仅将必须影响 _score 的子句保留在 bool.must 中(通常是全文 match)。

    原因: filter 上下文会跳过评分,并在重复查询时使用 filter/bitset 缓存。语义: 匹配的文档不变;只有评分和性能会改变——请明确说明这一点。

    改写示例模式:

    {
      "query": {
        "bool": {
          "filter": [{ "term": { "status": "active" } }, { "term": { "tenant_id": "acme" } }],
          "must": [{ "match": { "description": "wireless keyboard" } }]
        }
      }
    }
    
    4b. 消除前导通配符

    当性能分析显示存在 WildcardQuery,其 description 类似 message:*timeout*,且 next_doc 耗时较高时,前导的 * 会阻止索引查找。请根据映射和用户意图(子串、前缀或精确匹配)选择修复方案:

    意图推荐改写
    日志中的全文子串在经过分析的 message text 字段上使用 match 或 match_phrase
    keyword 上的字面子串使用 wildcard 类型字段,或使用 ngram 分析器重新索引
    仅前缀(timeout*)在 keyword 上使用 prefix 查询,或在索引时使用 edge ngram

    同时,将任何不参与评分的精确匹配(例如,对 keyword 使用 { "match": { "service": "checkout" } })移入 filter——映射确认字段为 keyword 时,请在该字段上使用 term。

    改写示例模式:

    {
      "query": {
        "bool": {
          "filter": [{ "term": { "service.keyword": "checkout" } }],
          "must": [{ "match": { "message": "timeout" } }]
        }
      }
    }
    

    请根据第 3 步中的映射调整字段名(service 与 service.keyword)。

    4c. 可选——性能分析前验证改写

    当语义不确定时(例如将 wildcard 改为 match 可能会包含 wildcard 排除的经分析词元),请使用改写后的查询调用 POST /{index}/_validate/query?explain=true,并查看说明以发现明显的不匹配。

    **决策:**选择能解决已分析成本的最小改写。**所需数据:**改写后的 Query DSL 请求体。

  5. 重新分析改写后的查询并进行比较。 再次调用 POST /{index}/_search,将 "profile": true 与改写后的查询一同传入。将 took 和排名靠前的 profile collector 与第 2 步的基线进行比较。

    **决策:**只有当主要 collector 发生变化或 time_in_nanos 显著下降时,才报告成功。如果 profile 仍显示前导通配符或参与评分的过滤条件,请继续迭代——不要仅凭 took 宣布成功,必须有 profile 确认。

    **所需数据:**改写前后 profile 摘要(排名靠前的 collector 的 type、description、time_in_nanos)。

  6. 按以下顺序报告发现。

    1. 根本原因——引用 profile(例如“WildcardQuery message:*timeout* ≈ 3.8s,主要耗时在 next_doc”)。
    2. 改写——展示优化后的 bool 结构,并区分 filter 和 must。
    3. 映射说明——引用 GET /{index}/_mapping 确认 keyword 与 text 字段。
    4. 实测改进——提供第 5 步的改写前后 profile 或 took。
    5. 语义注意事项——仅当改写可能改变匹配的文档时说明(例如 match 与子串 wildcard 的差异)。

指南

  • 先进行性能分析。 如果用户提供了性能分析摘要,请使用它;但仍建议在更改后重新进行性能分析。
  • filter 用于精确匹配,must 用于相关性。 当文本查询决定排名时,状态、租户 ID、服务名称和时间范围通常不应放在 must 中。
  • 前导通配符几乎从来都不是日志搜索的正确解决方案。 优先使用经过分析的 match/match_phrase;仅在 keyword 或 wildcard 类型字段上使用 wildcard 匹配后缀模式(timeout*)。
  • 不要混淆慢与错。 慢查询也可能返回正确结果;优化应保留结果集,除非你明确说明存在语义上的权衡。
  • 深入参考: 性能分析收集器类型、过滤器缓存行为和通配符替代方案 — references/query-optimization-reference.md。

示例

must 中的未计分词项

输入: bool.must 包含对 status 的 term、对 tenant_id 的 term,以及对 description 的 match。

诊断: 性能分析显示,除了 MatchQuery,还有带计分的 TermQuery 收集器;精确过滤无需计分。

修复: 将两个 term 子句移至 filter;将 match 保留在 must 中。确认 status 和 tenant_id 的类型为 keyword。

前导通配符导致延迟居高不下

输入: must 中包含 wildcard message:*timeout*,以及对 service 的 match。性能分析结果:WildcardQuery 约耗时 3.8 秒。

诊断: 前导 * 会强制枚举词项;这不是索引或分片问题。

修复: 对经过分析的 message 使用 match;将服务条件移至 filter,并对 keyword 字段使用 term。重新进行性能分析 — 预计 WildcardQuery 将消失,或耗时缩短至可忽略不计。

操作

HTTP API(简写)elastic CLI 命令
GET /elastic es info
GET /_cat/indiceselastic es cat indices --index '<pattern>'
GET /{index}/_mappingelastic es indices get-mapping --index '<index>'
POST /{index}/_searchelastic es search --index '<index>' --input-file '<search-body.json>'
POST /{index}/_validate/query?explain=trueelastic es indices validate-query --index '<index>' --explain true --query '<json>'

在第 2 步和第 5 步进行性能分析时,请在搜索 JSON 请求体中加入 "profile": true(或传入 --profile true)。

评论

支持 Markdown