关于 elasticsearch-query-optimization
利用 profile 证据诊断并优化缓慢的 Elasticsearch Query DSL 搜索。它针对现有查询,尽可能保持匹配语义,并在改写后重新测量性能。
主要功能
- 01通过 profile 定位主要耗时
- 02改写前核对字段映射
- 03将无需评分的条件移至 filter
- 04替换开销较高的前导通配符
- 05比较优化前后的 profile
适用场景
- 01排查缓慢的 Query DSL 搜索
- 02减少精确匹配条件的评分开销
- 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)时,绝不要将“添加分片”或“升级硬件”作为主要解决方案。先修复查询;只有证据表明查询已达到最优时,才考虑基础设施变更。
流程
-
**确认连接并找到目标索引。**调用
GET /。如果调用失败,请停止操作——不要猜测端点 或凭据。如果用户提供了索引模式(例如logs-*),通过GET /_cat/indices缩小候选范围,并选择查询实际针对的索引或模式。**决策:**只有在确认索引后才能继续。**所需数据:**索引名称或模式,以及耗时较长的 Query DSL 请求体(由用户提供或来自已保存的搜索)。
-
**对耗时较长的查询进行 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)。 解释诊断时,逐字引用耗时最高的项目。 -
**重写前检查字段映射。**调用
GET /{index}/_mapping。对于每个要移动或重写的子句,确认字段类型:- 对精确值使用
term/terms/filter——字段必须为keyword(或其他非分析类型)。在text字段上使用term是常见错误;如果类型不正确,应指出问题并建议使用正确的子字段(例如service.keyword)或修改映射——不要悄悄重写。 match/match_phrase——目标应为text字段(已分析)。wildcard——可用于keyword或wildcard类型;无论字段类型如何,前导*仍会导致扫描。
**决策:**只有在字段类型已确认的情况下,才能提出重写方案。**所需数据:**查询中引用的每个字段的映射。
- 对精确值使用
-
重写查询以消除 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耗时较高时,前导的*会阻止索引查找。请根据映射和用户意图(子串、前缀或精确匹配)选择修复方案:意图 推荐改写 日志中的全文子串 在经过分析的 messagetext字段上使用match或match_phrasekeyword 上的字面子串 使用 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 请求体。
- 将精确匹配子句移入
-
重新分析改写后的查询并进行比较。 再次调用
POST /{index}/_search,将"profile": true与改写后的查询一同传入。将took和排名靠前的 profile collector 与第 2 步的基线进行比较。**决策:**只有当主要 collector 发生变化或
time_in_nanos显著下降时,才报告成功。如果 profile 仍显示前导通配符或参与评分的过滤条件,请继续迭代——不要仅凭took宣布成功,必须有 profile 确认。**所需数据:**改写前后 profile 摘要(排名靠前的 collector 的
type、description、time_in_nanos)。 -
按以下顺序报告发现。
- 根本原因——引用 profile(例如“
WildcardQuerymessage:*timeout*≈ 3.8s,主要耗时在next_doc”)。 - 改写——展示优化后的 bool 结构,并区分 filter 和 must。
- 映射说明——引用
GET /{index}/_mapping确认 keyword 与 text 字段。 - 实测改进——提供第 5 步的改写前后 profile 或
took。 - 语义注意事项——仅当改写可能改变匹配的文档时说明(例如
match与子串wildcard的差异)。
- 根本原因——引用 profile(例如“
指南
- 先进行性能分析。 如果用户提供了性能分析摘要,请使用它;但仍建议在更改后重新进行性能分析。
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/indices | elastic es cat indices --index '<pattern>' |
GET /{index}/_mapping | elastic es indices get-mapping --index '<index>' |
POST /{index}/_search | elastic es search --index '<index>' --input-file '<search-body.json>' |
POST /{index}/_validate/query?explain=true | elastic es indices validate-query --index '<index>' --explain true --query '<json>' |
在第 2 步和第 5 步进行性能分析时,请在搜索 JSON 请求体中加入 "profile": true(或传入 --profile true)。