elasticsearch-query-optimization について
profile の根拠を使い、遅い Elasticsearch Query DSL 検索を診断・最適化するスキルです。既存クエリを対象に、可能な限り検索結果の意味を保ちながら書き換え、再測定します。
主な機能
- 01profile で主な処理コストを特定
- 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 検索が遅い理由を診断し、プロファイルから(推測ではなく)主なコストを特定します。一致の意味を維持しながら、そのコストを取り除くようにクエリを書き換え、プロファイリングを有効にして再計測します。
環境設定
このスキルは、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)が示されている場合、主な対処として「シャードを追加する」または「ハードウェアを増強する」と提案してはいけません。まずクエリを修正してください。インフラストラクチャの変更には、クエリがすでに最適化されているという根拠が必要です。
プロセス
-
接続を確認し、対象インデックスを特定します。
GET /を呼び出します。呼び出しに失敗した場合は、処理を中止してください。エンドポイントや認証情報を推測してはいけません。ユーザーがインデックスパターン(例:logs-*)を指定した場合は、GET /_cat/indicesで候補を絞り込み、クエリが実際に対象とするインデックスまたはパターンを選びます。判断基準: インデックスが判明した場合にのみ続行します。必要なデータ: インデックス名またはパターン、および遅い Query DSL のボディ(ユーザーから提供されたもの、または保存済み検索から取得したもの)。
-
遅いクエリをプロファイリングし、主なコストを特定します。
"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)を含むプロファイルツリーです。診断を説明するときは、最大の要因をそのまま引用してください。 - スコアリング句と並んで
-
書き換える前にフィールドマッピングを調べます。
GET /{index}/_mappingを呼び出します。移動または書き換える各句について、フィールドタイプを確認します。- 完全一致値に対する
term/terms/filter— フィールドはkeyword(または分析されない別の型)である必要があります。textフィールドに対するtermはよくある不具合です。型が適切でない場合は、その旨を伝え、適切なサブフィールド(例:service.keyword)またはマッピングの変更を提案します。黙って書き換えてはいけません。 match/match_phrase— 対象はtextフィールド(分析済み)にします。wildcard—keyword型またはwildcard型で使用できます。ただし、先頭の*がある場合は、型にかかわらず走査が強制されます。
判断基準: 確認済みの型に適合する書き換えのみを提案します。必要なデータ: クエリで参照されている各フィールドのマッピングです。
- 完全一致値に対する
-
プロファイルで特定したボトルネックを取り除くようにクエリを書き換えます。
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の時間が長い場合、先頭の*によりインデックス検索が妨げられています。マッピングとユーザーの意図(部分文字列、プレフィックス、完全一致)に応じて修正方法を選びます。意図 推奨する書き換え ログ内の全文部分文字列 分析済みの messagetextフィールドに対する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 ボディ。
- 完全一致の句を
-
書き換えたクエリを再プロファイリングして比較します。
"profile": trueと書き換えたクエリを指定して、再度POST /{index}/_searchを呼び出します。tookと、ステップ 2 のベースラインに対するプロファイルの最上位コレクターを比較します。判断: 支配的なコレクターが変わったか、
time_in_nanosが大幅に低下した場合にのみ、成功と報告します。プロファイルに先頭のワイルドカードやスコア計算されるフィルターがまだ表示される場合は、改善を繰り返します。プロファイルで確認せず、tookだけを根拠に改善したと判断しないでください。必要なデータ: 変更前/変更後のプロファイル概要(最上位コレクターの
type、description、time_in_nanos)。 -
以下の順序で調査結果を報告します。
- 根本原因 — プロファイルの内容を引用します(たとえば「
WildcardQuerymessage:*timeout*≈ 3.8s、時間の大部分はnext_doc」)。 - 書き換え —
filterとmustを分けた最適化後の bool 構造を示します。 - マッピングに関する注記 —
GET /{index}/_mappingで確認した keyword と text の情報を示します。 - 測定した改善 — ステップ 5 の変更前/変更後のプロファイルまたは
tookを示します。 - 意味上の注意点 — 書き換えによって一致するドキュメントが変わる可能性がある場合にのみ記載します(たとえば
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/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 を渡してください)。