mcpField
E
elasticsearch-query-optimization

elasticsearch-query-optimization

github.com/elasticデータ・データベース6021.1k 回インストール今日更新
リポジトリ

elasticsearch-query-optimization について

profile の根拠を使い、遅い Elasticsearch Query DSL 検索を診断・最適化するスキルです。既存クエリを対象に、可能な限り検索結果の意味を保ちながら書き換え、再測定します。

主な機能

  1. 01profile で主な処理コストを特定
  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 検索が遅い理由を診断し、プロファイルから(推測ではなく)主なコストを特定します。一致の意味を維持しながら、そのコストを取り除くようにクエリを書き換え、プロファイリングを有効にして再計測します。

環境設定

このスキルは、elastic CLI を介して Elasticsearch の操作を実行します。 elastic CLI がインストールされていない場合は、何に必要なのかをユーザーに伝えてください。認証情報を推測したり、HTTP API を直接呼び出したり、ほかの回避策を試したりしないでください。

このスキルでは、HTTP の省略表記(例:GET /、GET /_cat/indices、GET /{index}/_mapping、 GET /{index}/_settings/index.mode、POST /_query)で操作を記載しています。このドキュメントの末尾にある 操作の表で、各省略表記に対応する elastic CLI コマンドを示しています。HTTP API を直接呼び出すのではなく、必ず CLI を使用してください。

対象範囲: POST /{index}/_search を介した Query DSL 検索です。このスキルでは、クエリを ES|QL に移行せず、ユーザーが現在実行している bool/match/term/wildcard の構造を最適化します。

基本原則: プロファイルで特定の句(たとえば、約 3.8 秒かかる WildcardQuery)が示されている場合、主な対処として「シャードを追加する」または「ハードウェアを増強する」と提案してはいけません。まずクエリを修正してください。インフラストラクチャの変更には、クエリがすでに最適化されているという根拠が必要です。

プロセス

  1. 接続を確認し、対象インデックスを特定します。 GET / を呼び出します。呼び出しに失敗した場合は、処理を中止してください。エンドポイントや認証情報を推測してはいけません。ユーザーがインデックスパターン(例:logs-*)を指定した場合は、GET /_cat/indices で候補を絞り込み、クエリが実際に対象とするインデックスまたはパターンを選びます。

    判断基準: インデックスが判明した場合にのみ続行します。必要なデータ: インデックス名またはパターン、および遅い Query DSL のボディ(ユーザーから提供されたもの、または保存済み検索から取得したもの)。

  2. 遅いクエリをプロファイリングし、主なコストを特定します。 "profile": true を指定して POST /{index}/_search を呼び出し、ユーザーのクエリは変更せずに使用します。took を確認し、profile.shards[].searches[].query を調べます。子コレクターを time_in_nanos の降順に並べ、最も大きな要因を特定します。

    判断基準: プロファイルの根拠に基づいてボトルネックを分類します。

    • スコアリング句と並んで must 内にある TermQuery/PointRangeQuery/MatchNoDocsQuery — 完全一致または範囲フィルターに不要なスコアリングが行われています。考えられる修正:これらを filter コンテキストに移動します(手順 4a)。
    • 先頭に * がある WildcardQuery(例:message:*timeout*) — 転置インデックスを使用できず、ドキュメントごとに用語を走査します。考えられる修正:先頭のワイルドカードを削除します(手順 4b)。
    • text フィールドに対する MatchQuery — 予想されるスコアリングコストです。filter コンテキストへの変更後もプロファイルで支配的なコストだと示された場合に限り、最適化します。
    • aggregation の時間が長い — クエリの調整とは別の問題です。agg ツリーをプロファイリングしてください(ユーザーが agg について尋ねていない限り対象外です)。

    必要なデータ: 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 に移動する

    関連度を決める全テキスト検索の match とともに、キーワードに対する完全一致の term/terms/range/match(またはその他のスコアに影響しない意図の)句が must に含まれている場合:

    • 完全一致の句を bool.filter(または filter 配列のエントリ)に移動します。
    • _score に影響させる必要がある句のみを bool.must に残します(通常は全テキスト検索の match です)。

    理由: フィルターコンテキストではスコア計算が省略され、繰り返しのクエリではフィルター/ビットセットキャッシュが利用されます。意味: 一致するドキュメントは同じで、変化するのはスコアとパフォーマンスのみであることを明記してください。

    書き換え例:

    {
      "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
    キーワード上の文字どおりの部分文字列wildcard 型フィールド、または ngram アナライザーを使用して再インデックス
    プレフィックスのみ(timeout*)keyword に対する prefix クエリ、またはインデックス時の edge ngram

    スコアに影響しない完全一致(たとえば、キーワードに対する { "match": { "service": "checkout" } })も filter に移動します。マッピングで確認できる場合は、キーワードフィールドに term を使用してください。

    書き換え例:

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

    フィールド名(service と service.keyword など)は、ステップ 3 で確認したマッピングに合わせて調整してください。

    4c. 任意 — プロファイリング前に書き換えを検証する

    意味が不確かな場合(たとえば wildcard を match に変更すると、ワイルドカードでは除外されていた分析済みトークンが含まれる可能性がある場合)は、書き換えたクエリを指定して POST /{index}/_validate/query?explain=true を呼び出し、明らかな不一致がないか説明を確認します。

    判断: プロファイルで確認されたコストに対処できる、最小限の書き換えを選択します。必要なデータ: 書き換え後の Query DSL ボディ。

  5. 書き換えたクエリを再プロファイリングして比較します。 "profile": true と書き換えたクエリを指定して、再度 POST /{index}/_search を呼び出します。took と、ステップ 2 のベースラインに対するプロファイルの最上位コレクターを比較します。

    判断: 支配的なコレクターが変わったか、time_in_nanos が大幅に低下した場合にのみ、成功と報告します。プロファイルに先頭のワイルドカードやスコア計算されるフィルターがまだ表示される場合は、改善を繰り返します。プロファイルで確認せず、took だけを根拠に改善したと判断しないでください。

    必要なデータ: 変更前/変更後のプロファイル概要(最上位コレクターの type、description、time_in_nanos)。

  6. 以下の順序で調査結果を報告します。

    1. 根本原因 — プロファイルの内容を引用します(たとえば「WildcardQuery message:*timeout* ≈ 3.8s、時間の大部分は next_doc」)。
    2. 書き換え — filter と must を分けた最適化後の bool 構造を示します。
    3. マッピングに関する注記 — GET /{index}/_mapping で確認した keyword と text の情報を示します。
    4. 測定した改善 — ステップ 5 の変更前/変更後のプロファイルまたは took を示します。
    5. 意味上の注意点 — 書き換えによって一致するドキュメントが変わる可能性がある場合にのみ記載します(たとえば match と部分文字列 wildcard の違い)。

ガイドライン

  • まずプロファイルを確認してください。 ユーザーがプロファイルの概要を提示した場合は、それを使用してください。ただし、変更後に再度プロファイルを確認するよう推奨してください。
  • filter は等価性、must は関連性に使います。 テキストクエリでランキングを行う場合、ステータス、テナント ID、サービス名、時間範囲を must に含める必要はほとんどありません。
  • ログ検索で先頭ワイルドカードを使うのは、ほぼ間違った対処法です。 解析済みの match/match_phrase を優先し、wildcard はキーワード型または wildcard 型のフィールドでサフィックスパターン(timeout*)を検索する場合に限って使用してください。
  • 遅いことと誤っていることを混同しないでください。 遅いクエリでも正しい結果を返すことがあります。最適化によって結果セットが維持される場合は、意味上のトレードオフについて明示的に警告してください。
  • 詳しいリファレンス: プロファイルコレクターの種類、フィルターキャッシュの動作、ワイルドカードの代替案については、 references/query-optimization-reference.md を参照してください。

例

must 内のスコアリング不要な項目

入力: bool.must に status の term、tenant_id の term、description の match が含まれています。

診断: プロファイルには MatchQuery と並んでスコアリング対象の TermQuery コレクターが表示されています。完全一致フィルターにスコアリングは不要です。

修正: 2 つの term 条件を filter に移し、match は must に残します。status と tenant_id が keyword であることを確認してください。

先頭ワイルドカードがレイテンシの大半を占める

入力: must に wildcard の message:*timeout* と service の match があります。プロファイルでは、WildcardQuery が約 3.8 秒です。

診断: 先頭の * によって用語の列挙が強制されます。インデックスやシャードの問題ではありません。

修正: 解析済みの message に対して match を使用し、service はキーワード型の term として filter に移します。再度プロファイルを確認してください。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 を渡してください)。

コメント