elasticsearch-query-optimization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Elasticsearch Query DSL Optimization

Elasticsearch Query DSL 优化

Diagnose why a Query DSL search is slow, identify the dominant cost from the profile (not guesswork), rewrite the query to remove that cost while preserving match semantics, and re-measure with profiling enabled.
<!-- begin-partial: preamble -->
诊断Query DSL搜索缓慢的原因,通过分析结果(而非猜测)确定主要开销来源,重写查询以消除该开销同时保留匹配语义,并在启用分析的情况下重新衡量性能。
<!-- begin-partial: preamble -->

Environment Configuration

环境配置

This skill executes Elasticsearch operations through the
elastic
CLI. If the
elastic
CLI
is not installed, tell the user what it is needed for. Do not guess credentials, call the HTTP API directly, or attempt other workarounds.
This skill references operations in HTTP-shorthand form (e.g.,
GET /
,
GET /_cat/indices
,
GET /{index}/_mapping
,
GET /{index}/_settings/index.mode
,
POST /_query
). The Operations table at the end of this document maps each shorthand to the equivalent
elastic
CLI command — always use the CLI rather than calling the HTTP API directly.
<!-- end-partial: preamble -->
Scope: Query DSL searches via
POST /{index}/_search
. This skill does not migrate queries to ES|QL — it optimizes the existing bool/match/term/wildcard structure the user already runs.
Ground rule: Never recommend "add shards" or "scale hardware" as the primary fix when the profile names a specific clause (for example
WildcardQuery
at ~3.8s). Fix the query first; infrastructure changes require evidence the query is already optimal.
此技能通过
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。
<!-- end-partial: preamble -->
范围: 通过
POST /{index}/_search
执行的Query DSL搜索。此技能不会将查询迁移至ES|QL——它仅优化用户当前运行的现有bool/match/term/wildcard结构。
基本原则: 当分析结果明确指出具体子句(例如耗时约3.8秒的
WildcardQuery
)时,切勿将“添加分片”或“升级硬件”作为主要修复方案。优先修复查询;只有在证明查询已达最优状态后,才考虑基础设施变更。

Process

流程

  1. Confirm connectivity and locate the target index. Call
    GET /
    . If the call fails, stop — do not guess endpoints or credentials. When the user names an index pattern (for example
    logs-*
    ), narrow candidates with
    GET /_cat/indices
    and pick the index or pattern the query actually targets.
    Decision: proceed only when the index is known. Data needed: index name or pattern, and the slow Query DSL body (from the user or from a saved search).
  2. Profile the slow query to find the dominant cost. Call
    POST /{index}/_search
    with
    "profile": true
    and the user's query unchanged. Read
    took
    , then inspect
    profile.shards[].searches[].query
    — sort child collectors by
    time_in_nanos
    and identify the top contributor.
    Decision: classify the bottleneck from profile evidence:
    • TermQuery
      /
      PointRangeQuery
      /
      MatchNoDocsQuery
      inside
      must
      alongside a scoring clause
      — exact-match or range filters are being scored unnecessarily. Likely fix: move them to
      filter
      context (step 4a).
    • WildcardQuery
      with a leading
      *
      (for example
      message:*timeout*
      )
      — cannot use the inverted index; scans terms per document. Likely fix: remove the leading wildcard (step 4b).
    • MatchQuery
      on a
      text
      field
      — expected scoring cost; optimize only if profile shows it dominates after filter-context fixes.
    • High
      aggregation
      time
      — separate from query tuning; profile the agg tree (out of scope unless the user asked about aggs).
    Data needed: profile tree with
    type
    ,
    description
    ,
    time_in_nanos
    , and
    breakdown
    (especially
    next_doc
    for wildcards). Quote the top contributor verbatim when explaining the diagnosis.
  3. Inspect field mappings before rewriting. Call
    GET /{index}/_mapping
    . For every clause you will move or rewrite, confirm the field type:
    • term
      /
      terms
      /
      filter
      on exact values
      — field must be
      keyword
      (or another non-analyzed type). A
      term
      on a
      text
      field is a common bug; if types are wrong, say so and suggest the correct sub-field (for example
      service.keyword
      ) or a mapping change — do not silently rewrite.
    • match
      /
      match_phrase
      — target a
      text
      field (analyzed).
    • wildcard
      — works on
      keyword
      or
      wildcard
      types; leading
      *
      still forces a scan regardless of type.
    Decision: only propose rewrites that match confirmed types. Data needed: mapping for each field referenced in the query.
  4. Rewrite the query to remove the profiled bottleneck.

    4a. Move non-scoring clauses from
    must
    to
    filter

    When exact-match
    term
    /
    terms
    /
    range
    /
    match
    on a keyword (or other non-scoring intent) clauses sit in
    must
    alongside a full-text
    match
    that should drive relevance:
    • Move exact-match clauses into
      bool.filter
      (or a
      filter
      array entry).
    • Keep only clauses that must affect
      _score
      in
      bool.must
      (typically the full-text
      match
      ).
    Why: filter context skips scoring and participates in the filter/bitset cache on repeated queries. Semantics: the same documents match; only scoring and performance change — state this explicitly.
    Example rewrite pattern:
    json
    {
      "query": {
        "bool": {
          "filter": [{ "term": { "status": "active" } }, { "term": { "tenant_id": "acme" } }],
          "must": [{ "match": { "description": "wireless keyboard" } }]
        }
      }
    }

    4b. Eliminate leading wildcards

    When the profile shows
    WildcardQuery
    with
    description
    like
    message:*timeout*
    and high
    next_doc
    time, the leading
    *
    prevents index lookup. Choose a fix based on mapping and user intent (substring vs prefix vs exact):
    IntentPreferred rewrite
    Full-text substring in logs
    match
    or
    match_phrase
    on the analyzed
    message
    text
    field
    Literal substring on keyword
    wildcard
    -typed field, or reindex with ngram analyzer
    Prefix only (
    timeout*
    )
    prefix
    query on
    keyword
    , or edge ngram at index time
    Also move any non-scoring exact match (for example
    { "match": { "service": "checkout" } }
    on a keyword) into
    filter
    — use
    term
    on the keyword field when the mapping confirms it.
    Example rewrite pattern:
    json
    {
      "query": {
        "bool": {
          "filter": [{ "term": { "service.keyword": "checkout" } }],
          "must": [{ "match": { "message": "timeout" } }]
        }
      }
    }
    Adjust field names (
    service
    vs
    service.keyword
    ) to match the mapping from step 3.

    4c. Optional — validate rewrite before profiling

    When semantics are uncertain (for example changing
    wildcard
    to
    match
    may include analyzed tokens the wildcard excluded), call
    POST /{index}/_validate/query?explain=true
    with the rewritten query and read the explanation for obvious mismatches.
    Decision: pick the smallest rewrite that addresses the profiled cost. Data needed: rewritten Query DSL body.
  5. Re-profile the rewritten query and compare. Call
    POST /{index}/_search
    again with
    "profile": true
    and the rewritten query. Compare
    took
    and the top profile collector to the baseline from step 2.
    Decision: report success only when the dominant collector changed or
    time_in_nanos
    dropped materially. If the profile still shows a leading wildcard or scored filters, iterate — do not declare victory from
    took
    alone without profile confirmation.
    Data needed: before/after profile summaries (top collector
    type
    ,
    description
    ,
    time_in_nanos
    ).
  6. Report findings in this order.
    1. Root cause — quote the profile (for example "
      WildcardQuery
      message:*timeout*
      ≈ 3.8s, mostly
      next_doc
      ").
    2. Rewrite — show the optimized bool structure with filter vs must separation.
    3. Mapping notes — keyword vs text confirmations from
      GET /{index}/_mapping
      .
    4. Measured improvement — before/after profile or
      took
      from step 5.
    5. Semantic caveat — only if the rewrite could change which documents match (for example
      match
      vs substring
      wildcard
      ).
  1. 确认连接性并定位目标索引。调用
    GET /
    。如果调用失败,请停止操作——请勿猜测端点或凭据。当用户指定索引模式(例如
    logs-*
    )时,使用
    GET /_cat/indices
    缩小候选范围,并选择查询实际指向的索引或模式。
    决策: 仅在确定索引后继续。所需数据: 索引名称或模式,以及缓慢的Query DSL查询体(来自用户或已保存的搜索)。
  2. 分析缓慢查询以找到主要开销。调用
    POST /{index}/_search
    ,添加
    "profile": true
    参数并保持用户查询不变。读取
    took
    值,然后检查
    profile.shards[].searches[].query
    ——按
    time_in_nanos
    对子收集器排序,确定主要贡献者。
    决策: 根据分析证据对瓶颈进行分类:
    • must
      中包含
      TermQuery
      /
      PointRangeQuery
      /
      MatchNoDocsQuery
      以及评分子句
      ——精确匹配或范围过滤器被不必要地评分。可能的修复:将它们移至
      filter
      上下文(步骤4a)。
    • 带有前导
      *
      WildcardQuery
      (例如
      message:*timeout*
      ——无法使用倒排索引;需逐个文档扫描词条。可能的修复:移除前导通配符(步骤4b)。
    • text
      字段上的
      MatchQuery
      ——预期的评分开销;仅当分析结果显示在修复过滤器上下文后该开销仍占主导时才进行优化。
    • aggregation
      耗时
      ——与查询调优分离;需分析聚合树(除非用户询问聚合,否则不在本技能范围内)。
    所需数据: 包含
    type
    description
    time_in_nanos
    breakdown
    (尤其是通配符的
    next_doc
    )的分析树。解释诊断时请逐字引用主要贡献者。
  3. 重写前检查字段映射。调用
    GET /{index}/_mapping
    。对于每个将要移动或重写的子句,确认字段类型:
    • 精确值上的
      term
      /
      terms
      /
      filter
      ——字段必须为
      keyword
      (或其他非分析类型)。
      text
      字段上使用
      term
      是常见错误;如果类型错误,请告知用户并建议正确的子字段(例如
      service.keyword
      )或映射变更——请勿静默重写。
    • match
      /
      match_phrase
      ——目标为
      text
      字段(已分析)。
    • wildcard
      ——适用于
      keyword
      wildcard
      类型;无论类型如何,前导
      *
      仍会强制扫描。
    决策: 仅提出与已确认类型匹配的重写方案。所需数据: 查询中引用的每个字段的映射。
  4. 重写查询以消除分析出的瓶颈

    4a. 将非评分子句从
    must
    移至
    filter

    当精确匹配的
    term
    /
    terms
    /
    range
    /
    match
    (针对keyword或其他非评分意图)子句与应驱动相关性的全文
    match
    一同位于
    must
    中时:
    • 将精确匹配子句移至
      bool.filter
      (或
      filter
      数组条目)。
    • 仅保留必须影响
      _score
      的子句在
      bool.must
      中(通常为全文
      match
      )。
    原因: 过滤器上下文会跳过评分,并且在重复查询时参与过滤器/位集缓存。语义: 匹配的文档保持不变;仅评分和性能发生变化——请明确说明这一点。
    重写示例模式:
    json
    {
      "query": {
        "bool": {
          "filter": [{ "term": { "status": "active" } }, { "term": { "tenant_id": "acme" } }],
          "must": [{ "match": { "description": "wireless keyboard" } }]
        }
      }
    }

    4b. 消除前导通配符

    当分析结果显示
    WildcardQuery
    的描述类似于
    message:*timeout*
    next_doc
    耗时较高时,前导
    *
    会阻止索引查找。根据映射和用户意图(子字符串、前缀或精确匹配)选择修复方案:
    意图首选重写方案
    日志中的全文子字符串在已分析的
    message
    text
    字段上使用
    match
    match_phrase
    关键字上的字面子字符串使用
    wildcard
    类型字段,或使用ngram分析器重新索引
    仅前缀匹配(
    timeout*
    keyword
    上使用
    prefix
    查询,或在索引时使用edge ngram分析器
    同时将任何非评分的精确匹配(例如针对keyword字段的
    { "match": { "service": "checkout" } }
    )移至
    filter
    ——当映射确认后,在keyword字段上使用
    term
    重写示例模式:
    json
    {
      "query": {
        "bool": {
          "filter": [{ "term": { "service.keyword": "checkout" } }],
          "must": [{ "match": { "message": "timeout" } }]
        }
      }
    }
    根据步骤3中的映射调整字段名称(
    service
    vs
    service.keyword
    )。

    4c. 可选——分析前验证重写结果

    当语义不确定时(例如将
    wildcard
    改为
    match
    可能包含通配符排除的已分析词条),调用
    POST /{index}/_validate/query?explain=true
    并传入重写后的查询,读取解释以检查明显的不匹配。
    决策: 选择最小的重写方案以解决分析出的开销。所需数据: 重写后的Query DSL查询体。
  5. 重新分析重写后的查询并进行比较。再次调用
    POST /{index}/_search
    ,添加
    "profile": true
    参数并使用重写后的查询。将
    took
    值和主要分析收集器与步骤2中的基准值进行比较。
    决策: 仅当主要收集器发生变化或
    time_in_nanos
    显著下降时,才报告优化成功。如果分析结果仍显示前导通配符或已评分过滤器,请迭代优化——请勿仅依据
    took
    值就宣告成功,必须有分析结果的确认。
    所需数据: 优化前后的分析摘要(主要收集器的
    type
    description
    time_in_nanos
    )。
  6. 按以下顺序报告结果
    1. 根本原因——引用分析结果(例如“
      WildcardQuery
      message:*timeout*
      ≈ 3.8秒,主要耗时在
      next_doc
      ”)。
    2. 重写方案——展示优化后的bool结构,区分filter和must。
    3. 映射说明——来自
      GET /{index}/_mapping
      的keyword与text类型确认。
    4. 可衡量的改进——步骤5中的优化前后分析结果或
      took
      值。
    5. 语义注意事项——仅当重写可能改变匹配的文档集时(例如
      match
      与子字符串
      wildcard
      的区别)才说明。

Guidelines

指南

  • Profile first. If the user supplies a profile summary, use it — but still recommend re-profiling after changes.
  • Filter is for equality, must is for relevance. Status, tenant ID, service name, and time ranges rarely belong in
    must
    when a text query drives ranking.
  • Leading wildcards are almost never the right fix for log search. Prefer analyzed
    match
    /
    match_phrase
    ; reserve
    wildcard
    for suffix patterns (
    timeout*
    ) on keyword or
    wildcard
    -typed fields.
  • Do not conflate slow with wrong. A slow query can return correct results; optimization preserves the result set unless you explicitly warn about a semantic trade-off.
  • Deep reference: profile collector types, filter-cache behavior, and wildcard alternatives — references/query-optimization-reference.md.
  • 先分析。如果用户提供了分析摘要,请使用它——但仍建议在变更后重新分析。
  • Filter用于相等匹配,Must用于相关性。状态、租户ID、服务名称和时间范围在文本查询驱动排名时,很少属于
    must
  • 前导通配符几乎永远不是日志搜索的正确解决方案。优先使用已分析的
    match
    /
    match_phrase
    ;仅在keyword或
    wildcard
    类型字段上保留
    wildcard
    用于后缀模式(
    timeout*
    )。
  • 请勿混淆缓慢与错误。缓慢的查询可能返回正确的结果;优化会保留结果集,除非您明确警告存在语义权衡。
  • 深度参考: 分析收集器类型、过滤器缓存行为和通配符替代方案——references/query-optimization-reference.md

Examples

示例

Unscored terms in
must

must
中的非评分term

Input:
bool.must
contains
term
on
status
,
term
on
tenant_id
, and
match
on
description
.
Diagnosis: profile shows scored
TermQuery
collectors alongside
MatchQuery
; exact filters do not need scoring.
Fix: move both
term
clauses to
filter
; keep
match
in
must
. Confirm
status
and
tenant_id
are
keyword
.
输入:
bool.must
包含
status
上的
term
tenant_id
上的
term
以及
description
上的
match
诊断: 分析结果显示
TermQuery
收集器与
MatchQuery
一同被评分;精确过滤器无需评分。
修复: 将两个
term
子句移至
filter
;将
match
保留在
must
中。确认
status
tenant_id
keyword
类型。

Leading wildcard dominates latency

前导通配符主导延迟

Input:
wildcard
message:*timeout*
plus
match
on
service
in
must
. Profile:
WildcardQuery
~3.8s.
Diagnosis: leading
*
forces term enumeration; not an index/shard problem.
Fix:
match
on analyzed
message
; move service to
filter
as
term
on keyword. Re-profile — expect
WildcardQuery
to disappear or shrink to negligible time.
输入:
must
中包含
message:*timeout*
wildcard
以及
service
上的
match
。分析结果:
WildcardQuery
耗时约3.8秒。
诊断: 前导
*
强制枚举词条;并非索引/分片问题。
修复: 在已分析的
message
字段上使用
match
;将service作为keyword上的
term
移至
filter
。重新分析——预期
WildcardQuery
会消失或耗时降至可忽略水平。

Operations

操作

HTTP API (shorthand)
elastic
CLI command
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>'
Include
"profile": true
in the search JSON body (or pass
--profile true
) when profiling in steps 2 and 5.
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
参数)。