Claude Media
Claude CodeでMongoDBのexplain()を読み、インデックスを設計する

Claude CodeでMongoDBのexplain()を読み、インデックスを設計する

MongoDB MCPのexplainツールで実行統計を取り、ESRガイドラインで複合インデックス案を出させ、hintで比較する手順をまとめます。

MongoDBのクエリが遅いとき、Claude Codeに「インデックスを足して」とだけ頼むと、推測ベースの案が返ってきます。先にexplain("executionStats")の数値を読ませ、複合インデックスのキー順をESRガイドラインで決めさせると、案の根拠が数字で追えます。この記事では、読み取り専用で接続したMongoDB MCPのexplainツールを使い、実行統計の読み取りからインデックス案の検証までを順に進めます。

接続そのものの手順はMongoDB MCPサーバーの接続ガイド、読み取り専用の設定は本番向けの読み取り専用設定にあります。ここでは接続済みの前提で、クエリ調整の部分だけを扱います。

読み取り専用で登録し、書き込み系ツールを外す

調整作業の主役は読むことです。インデックスの作成は人が判断してから実行するので、Claude Code側には書き込みの手段を渡しません。stdioサーバーとして登録するコマンドは次の形です。--envのあとにサーバー名を直接置くと、名前が環境変数の組として読まれて拒否されます。--transport stdioを間に挟みます。

claude mcp add --env MDB_MCP_CONNECTION_STRING="$MONGO_RO_URI" \
  --transport stdio mongodb -- \
  npx -y mongodb-mcp-server@latest --readOnly

$MONGO_RO_URIには、readロールだけを持つ専用ユーザーの接続文字列を入れます。--readOnlyはMCPサーバーを、読み取り・接続・メタデータの操作を行うツールだけに制限します。ただしDBユーザー自体の権限は変わりません。サーバー側の設定とDBユーザーの権限を重ねるのが、公式の推奨する構成です。

登録後に/mcpでツール一覧を開き、今回使う2つが見えることを確かめます。

  • explain: 評価対象のメソッドについて、勝ち残った実行計画の統計を返す
  • collection-indexes: コレクションに既にあるインデックスを返す

この2つが--readOnlyで残るかどうかは、ツール一覧の表には書かれていません。実際に登録されたツール名を見て判断します。

create-indexとdrop-indexを拒否ルールにも入れる

--readOnlyが効いていても、権限の拒否ルールを別に置いておくと、設定の食い違いがあったときの最後の砦になります。MCPツールの指定はmcp__<サーバー名>__<ツール名>の形です。プロジェクトの.claude/settings.jsonに次の2行を足します。

{
  "permissions": {
    "deny": [
      "mcp__mongodb__create-index",
      "mcp__mongodb__drop-index"
    ]
  }
}

MongoDB MCPサーバーのツール一覧には、create-indexとdrop-indexが載っています。インデックスは作成も削除も、本番では書き込みの影響が大きい操作です。この調整手順では使わないので、最初から封じておきます。

explain("executionStats")の数字を3つ読む

explainには詳細度のモードがあります。既定のqueryPlannerは、オプティマイザーが選んだ計画を返すだけで、クエリは実行しません。executionStatsは勝った計画を最後まで実行して、実行時の統計を返します。調整にはこちらが要ります。つまり本番に向けて実行すると、そのクエリの負荷が実際にかかります。

読む数字は次の3つです。

フィールド意味見るポイント
nReturned意味勝った計画が返したドキュメント数見るポイント分母になる
totalKeysExamined意味走査したインデックスエントリ数見るポイント0ならインデックスを使っていない
totalDocsExamined意味調べたドキュメント数(返した数ではない)見るポイントnReturnedとの差が無駄な読み取り

公式の例で確かめます。sample_mflix.moviesの21,349件に対して、yearが2000〜2005のクエリは3,809件にマッチします。インデックスが無いとwinningPlanのステージはCOLLSCANで、totalKeysExaminedは0、totalDocsExaminedは21,349です。3,809件を返すために全件を読んでいます。yearにインデックスを作ると、ステージはFETCHの下にIXSCANが付き、totalKeysExaminedとtotalDocsExaminedはどちらも3,809になりました。

調整の目標は、totalDocsExaminedとtotalKeysExaminedをnReturnedに近づけることです。比率で言えば、totalDocsExamined / nReturnedが1に近いほど、無駄な読み取りが少ない状態です。

もうひとつ見るのはステージ名です。COLLSCANはコレクションの全走査、IXSCANはインデックスキーの走査です。FETCHの子でないIXSCANは、インデックスだけでクエリを満たす状態を指します。集計の場合は、出力の最上位にstages配列が出て、先頭の$cursorがデータ取得の部分を持ちます。ここに$sortが$cursorの外に残っていれば、メモリ上でソートしているサインです。

Claudeに渡す依頼文とCLAUDE.mdの規約

数字の読み方を毎回説明するのは手間なので、手順をプロジェクトのCLAUDE.mdに書いておきます。次のような断片です。

## MongoDBのクエリ調整
 
- 調整の対象クエリは、まず `collection-indexes` で既存インデックスを確認する
- `explain` は `executionStats` で実行し、`nReturned` / `totalKeysExamined` /
  `totalDocsExamined` と `winningPlan` のステージ名を表にして報告する
- インデックス案はESR(Equality, Sort, Range)の順でキーを並べる
- 案は `createIndex` の文として提示するだけにし、MCPからは作成しない
- 案ごとに `hint()` を付けた `explain` の確認用クエリを併記する

そのうえで依頼は、対象のクエリをそのまま渡します。

orders コレクションで次のクエリが遅い。
find({ customerId: "c-1024", status: "shipped", total: { $gte: 5000 } })
  .sort({ createdAt: -1 })
既存インデックスと executionStats を確認して、インデックス案を出して。

Claudeはcollection-indexesとexplainを順に呼び、数字を表にして返します。ここで見るのは、COLLSCANかどうか、totalDocsExaminedがnReturnedよりどれだけ多いか、SORTステージがあるか、の3点です。数字を読むところまでは任せられますが、キー順の判断は次の節の規則で人が点検します。

ESRで複合インデックスのキー順を決める

複合インデックスのキーは、Equality(等価)、Sort(ソート)、Range(範囲)の順に並べるのが基本です。等価条件のフィールドを先頭に置くと、残りのキーが並び順を保てます。ソートを範囲より前に置くと、メモリ上のソートを避けられます。

公式の例は、directorsが"David Lynch"、runtimeが130未満、yearで並べ替えるクエリです。等価がdirectors、範囲がruntime、ソートがyearなので、最適なインデックスは次になります。

db.movies.createIndex({ directors: 1, year: 1, runtime: 1 })

先ほどのordersの例に当てはめると、等価がcustomerIdとstatus、ソートがcreatedAt、範囲がtotalです。案は{ customerId: 1, status: 1, createdAt: -1, total: 1 }の形になります(等価キー同士の順序は入れ替えられます)。

くらべる

範囲を先にするか、ソートを先にするか

ソート優先

ESR

メモリ上のソートを避けたいときの基本形です。ソートのキーを範囲より前に置きます。

範囲優先

ERS

範囲条件の絞り込みがとても強いときは、範囲をソートより前に置きます。ソート対象の件数が減るためです。

Claudeが出した案を点検するときの落とし穴は、次の3つです。

  • $ne・$nin・$regexは等価ではなく範囲として扱う
  • $inを単独で使うと等価。.sort()と組み合わせたとき、要素が200個以下なら等価に近い扱いでSORT_MERGEステージに展開され、201個以上は範囲と同じ扱いになる。この境界はMongoDBのバージョンで変わる可能性があると公式が注記している
  • インデックスがソートを支えるのは、ソートキーより前のキーすべてに等価条件があるときだけ

等価キーの数が多いと案の組み合わせが増えます。公式は、等価条件が選択的であるほどインデックス付きクエリの効率が上がると説明しています。等価キーの中でどれを先に置くかは、実測で決めます。

hintで案を比べる

案が出たら、効果を実測します。MongoDB公式の手順では、hint()とexplain()を組み合わせて、複数のインデックスの効率を並べます。公式の例ではyearの範囲とrated: "PG"の等価を組み合わせたクエリで、2つの複合インデックスを比べています。

インデックスtotalKeysExaminednReturned
{ year: 1, rated: 1 }(範囲が先頭)totalKeysExamined246nReturned235
{ rated: 1, year: 1 }(等価が先頭)totalKeysExamined235nReturned235

等価を先頭にした後者は、返す件数と同じ235キーの走査で済みました。ESRの考え方と一致する結果です。

自分のクエリでも同じ形で比較します。Claudeには次のように頼みます。

db.orders.find(
  { customerId: "c-1024", status: "shipped", total: { $gte: 5000 } }
).sort({ createdAt: -1 })
 .hint({ customerId: 1, status: 1, createdAt: -1, total: 1 })
 .explain("executionStats")

ただしhint()で指定したインデックスが存在しなければ、クエリはエラーになります。読み取り専用の接続では、インデックスを作れません。案の検証は、本番のコピーやステージングなど、インデックスを作ってよい環境で行います。

手順

本番を触らずに案を確かめる流れ

  1. 1

    本番で現状を記録する

    読み取り専用の接続でexplain("executionStats")を取り、3つの数字をメモします。

  2. 2

    コピー環境にインデックス案を作る

    ステージングなど、書き込みが許されるデータベースに案をcreateIndexします。コピーのデータ量が本番と近いほど、結果が当てになります。

  3. 3

    hintを付けてexplainを比べる

    案ごとにhint()を付けてexecutionStatsを取り、totalDocsExaminedとtotalKeysExaminedを表で並べます。

  4. 4

    採用案を本番に適用する

    差が出た案を、人が本番に作成します。作成後に、元のクエリをhint()なしで再実行して、計画が選ばれていることを確かめます。

explainは既存のプランキャッシュのエントリを無視し、新しいエントリも作りません。そのため、hint()なしで再実行したexplainは、キャッシュ済みの計画に引きずられず、オプティマイザーがその時点で選ぶ計画を示します。

点検で見つかる典型的な失敗

Claudeの案を実測したときに、次のような食い違いが起きます。

  • ソート用のキーが足りない: winningPlanにSORTステージが残っている。ソートキーが範囲より後ろにあるか、等価のプレフィックスが欠けている
  • 案が既存インデックスと重なる: collection-indexesで既存の複合インデックスの先頭が同じなら、新しいインデックスを作らずに、キーを後ろに足す形に置き換えられないか検討する
  • 件数の比が改善しない: 等価条件が少ない、または選択性が低い。キー順ではなく、クエリの条件そのものを見直す

どの結果が出ても、Claudeに「なぜこの順序か」を等価・ソート・範囲の言葉で説明させると、案の誤りに気づきやすくなります。

別のデータベースで同じ型の調整をする場合は、PostgreSQLのEXPLAIN ANALYZEで索引案を検証する手順やMySQLのスロークエリからインデックス案を出させる手順が参考になります。実行計画の読み方は違いますが、「数字で読み、案を出させ、コピーで検証する」流れは共通です。

まとめ

MongoDBのクエリ調整をClaude Codeに任せるときは、役割を分けると安全です。読むところ(collection-indexesとexplain)は読み取り専用の接続で任せ、作るところ(createIndex)は人が判断して、検証環境から本番へ進めます。キー順の点検は、ESRの3語で足ります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/mcp
  2. code.claude.com/docs/en/permissions
  3. mongodb.com/docs/manual/reference/explain-results
  4. mongodb.com/docs/manual/tutorial/analyze-query-plan
  5. mongodb.com/docs/manual/tutorial/equality-sort-range-guideline
  6. mongodb.com/docs/manual/reference/method/cursor.explain
  7. mongodb.com/docs/mcp-server/tools
  8. mongodb.com/docs/mcp-server/local-mcp/security-best-practices
この記事を共有:XはてブLinkedIn