このトピックでは、MongoDB のクエリプランナーの動作仕組み、クエリプランのキャッシュおよび評価方法、およびクエリの再計画が発生する原因について説明します。また、再計画に関する問題の特定方法と、それに対するソリューションも解説します。
クエリプランナー
指定されたクエリに対して、MongoDB のクエリプランナーは利用可能なインデックスに基づき、最も効率的なクエリプランを選択・キャッシュします。以下の図は、クエリプランナーの動作を示しています。
最も効率的なクエリプランの評価は、クエリプランナーが候補プランを評価する際に、クエリ実行計画が実行する作業単位(works)の数に基づいて行われます。キャッシュされたプランキャッシュエントリは、同一のクエリ形状を持つ後続の任意のクエリで再利用できます。
プランキャッシュエントリの状態
プランキャッシュエントリは、以下のいずれかの状態になります:
| 状態 | 説明 |
|---|---|
| なし | プランキャッシュ内にエントリが存在しません。クエリプランナーは、候補プランをゼロから評価する必要があります。 |
| Inactive(非アクティブ) | プランキャッシュ内に、過去の評価で記録された `works` 値を伴うエントリが存在します。このエントリは、アクティブ状態へと遷移可能です。 |
| Active(アクティブ) | プランキャッシュ内にエントリが存在し、マッチングクエリに使用されます。アクティブ状態の最適プランは、効率が低下した場合、非アクティブ状態へと再び遷移することがあります。 |
プランキャッシュの動作
プランキャッシュは完全にメモリ上に格納され、再起動時に永続化されません。以下の状況でキャッシュがクリアされます:
-
MongoDB データベースが再起動されます。
-
コレクションまたはインデックスが削除されます。
また、プランキャッシュにはサイズ制限があり、最近使用されていないエントリ(LRU:Least Recently Used)が優先的にキャッシュから削除される置換メカニズムが採用されています。
プランキャッシュ管理コマンド
特定のコレクションに対してプランキャッシュエントリを管理するため、以下のコマンドを実行できます:
| コマンド | 説明 |
|---|---|
db.<collection>.getPlanCache().clear() |
コレクションのプランキャッシュをクリアします。 |
db.<collection>.getPlanCache().listQueryShapes() |
コレクションのプランキャッシュ内に登録されているすべてのクエリ形状を一覧表示します。 |
db.<collection>.getPlanCache().getPlansByQuery(...) |
特定のクエリに対応するキャッシュ済みクエリプランを取得します。 |
キャッシュ済みクエリプランの取得例:
db.<collection>.getPlanCache().getPlansByQuery({"query": {"name": "testname"}, "sort": { "name": 1 }})
queryHash および planCacheKey
MongoDB 4.2 以降では、クエリ形状を識別するための queryHash が導入されています。各クエリ形状には、対応する queryHash 値が関連付けられます。また、MongoDB 4.2 では planCacheKey も導入されており、これは queryHash とは異なり、クエリ形状に加えて、その形状をサポート可能なインデックスの有無にも依存する値です。クエリ形状をサポート可能なインデックスが作成または削除されると、planCacheKey の値が変更される可能性がありますが、queryHash の値は変更されません。
例
以下のインデックスとクエリ形状を持つコレクションを想定します:
インデックス:
db.foo.createIndex( { x: 1 } )
db.foo.createIndex( { x: 1, y: 1 } )
db.foo.createIndex( { x: 1, z: 1 }, { partialFilterExpression: { x: { $gt: 10 } } } )
クエリ形状:
db.foo.explain().find( { x: { $gt: 5 } } ) // クエリ操作 1
db.foo.explain().find( { x: { $gt: 20 } } ) // クエリ操作 2
3 番目のインデックスは、x > 10 を必要とする部分フィルター式を使用します。したがって、このインデックスはクエリ操作 2 (x > 20) をサポートできますが、クエリ操作 1 (x > 5) はサポートできません。その結果、2 つのクエリの planCacheKey 値は異なります。新しいインデックス {x:1, a:1} が作成されると、両方のクエリ操作の planCacheKey 値が変更されます。
クエリの再計画
コレクション内のデータが大幅に変化すると、以前にキャッシュされたクエリプランが最適でなくなる場合があります。クエリプランナーはこれを検出し、クエリの効率を維持するためにプランを置き換えます。
キャッシュ済みプランのクエリ形状と一致するクエリを実行すると、クエリプランナーは候補プランを再評価せず、キャッシュ済みプランを直接使用します。ただし、クエリプランナーはキャッシュ済みプランの実行効率を継続的にモニターしています。キャッシュ済みプランの実行に必要な作業単位(works)が、予期値の 10 倍を超える場合、クエリプランナーは当該キャッシュエントリを削除し、すべての候補プランを再評価します。このプロセスを「クエリの再計画」といいます。
影響
-
頻繁なクエリ再計画は、クエリパフォーマンスを低下させる可能性があります。
-
過度なクエリ再計画は、ミューテックスロックの競合を引き起こし、CPU 使用率の高騰を招く可能性があります。
再計画の特定方法
スロークエリログ内で "replanned":true というキーワードを確認できます。このキーワードは、特定のクエリ形状に対して、クエリプランナーが一貫して効率的なプランを維持できなかったことを示します。
スロークエリログの例:
"replanned":true,"replanReason":"cached plan was less efficient than expected: expected trial execution to take X works but it took at least 10X works"
ソリューション
以下に示すソリューションは、推奨される長期的な修正策から即時対応策、最終手段まで、優先順位順に整理されています。
推奨:クエリおよびインデックスの最適化
クエリ再計画を防止するため、クエリ文を最適化し、効果的なインデックスを作成してください。ヒントワードやインデックスフィルターに頼るのではなく、クエリ、利用可能なインデックス、およびドキュメントのパターンを総合的に見直し、調整してください。
これは、再計画の根本原因に直接対処する手法であり、回避策ではなく、最も推奨されるアプローチです。
推奨:MongoDB のバージョンアップグレード
ご利用のインスタンスのメジャーバージョンが MongoDB 4.2 または MongoDB 4.4 の場合、マイナーバージョンを最新版に更新してください。これにより、ミューテックスロックの競合が大幅に軽減されます。また、メジャーバージョンを 5.0 または 6.0 へアップグレードすることで、これらの課題を解決できます。関連するカーネル修正の詳細については、「SERVER-40805」をご参照ください。
アップグレード手順については、「インスタンスのマイナーバージョンの更新」および「インスタンスのメジャーバージョンのアップグレード」をご参照ください。
プランキャッシュのクリア
プランキャッシュをクリアし、クエリプランナーがクエリに最も適したクエリプランを選択できるようにします。
hint() 関数の使用
アプリケーションコード内で、再計画が発生しやすいクエリに対して、クエリプランナーが使用すべきインデックスを明示的に指定するために hint() 関数を使用します。例:
db.<collection>.find({a:"ABC"},{b:1,_id:0}).sort({c:1}).hint({ a:1, c:1, b:1} )
クエリにヒントが指定されている場合、クエリプランナーが選択したプランは適用されず、代わりにヒントで指定されたインデックスが使用されます。
インデックスフィルターの使用
アプリケーションコード内で、再計画が発生しやすいクエリに対して、クエリプランナーが考慮可能なインデックスを制限するためにインデックスフィルターを使用します。例:
// インデックスフィルターの設定。
db.runCommand(
{
planCacheSetFilter: "<collection>",
query: { a: "ABC" },
projection: { b: 1, _id: 0 },
sort: { c: 1 },
indexes: [
{ a: 1, c: 1 , b: 1 }
]
}
)
// 既存のインデックスフィルターの削除。
db.runCommand(
{
planCacheClearFilters: "<collection>"
}
)
-
インデックスフィルターは、クエリプランナーによるクエリプラン選択における期待される動作をオーバーライドします。
-
クエリに対してヒントとインデックスフィルターの両方が指定されている場合、インデックスフィルターがヒントより優先されます。インデックスフィルターは慎重に使用してください。詳細については、「インデックスフィルター」をご参照ください。
インスタンスの仕様アップグレード
一時的な措置として、データベース負荷の軽減を目的に、インスタンスの仕様をアップグレードします。詳細については、「インスタンスの構成変更」をご参照ください。
テクニカルサポートへのお問い合わせ
上記のソリューションを試行しても問題が解消しない場合は、チケットを提出して、テクニカルサポートまでご連絡ください。