MongoDB のクエリプランの仕組み、再計画が発生する理由、および再計画に関する問題の解決方法について説明します。
クエリプランナー
MongoDB のクエリプランナーは、利用可能なインデックスに基づき、各クエリに対して最も効率的なクエリプランを選択してキャッシュします。
クエリプランナーは、各候補プランが必要とする作業単位数(works)に基づいて評価を行い、最適なプラン(勝者プラン)をキャッシュします。キャッシュされたエントリは、同じクエリ形状を持つクエリに対して再利用されます。
プランキャッシュのエントリには、次の 3 つの状態があります。
欠落:キャッシュ内にプランが存在しません。
非アクティブ:プランがキャッシュ内に存在し、
works値が評価済みで、アクティブに昇格可能です。アクティブ:キャッシュ内の勝者プランです。非アクティブに降格される可能性があります。
プランキャッシュはメモリ上に保存され、ディスクには永続化されません。MongoDB インスタンスの再起動時やコレクション・インデックスの削除時にクリアされます。また、キャッシュサイズには上限があり、LRU 方式でエビクションが行われます。
クエリプランを管理するには、次のコマンドを使用します。
コレクションのプランキャッシュをクリアします。
db.<collection>.getPlanCache().clear()コレクションのすべてのクエリ形状を一覧表示します。
db.<collection>.getPlanCache().list()説明PlanCache.listQueryShapes()は MongoDB 4.2 以降で非推奨です。PlanCache.list()を代わりに使用してください。特定のクエリ形状に対するキャッシュ済みプランを表示します。
db.<collection>.getPlanCache().list([{ $match: { "createdFromQuery.query": { "name": "testname" }, "createdFromQuery.sort": { "name": 1 } } }])説明PlanCache.getPlansByQuery()は MongoDB 4.2 以降で非推奨です。クエリ形状で絞り込むには、$matchステージを指定してPlanCache.list()を使用してください。
QueryHash と planCacheKey
MongoDB 4.2 以降では、各クエリ形状に一意の queryHash が割り当てられます。planCacheKey はクエリ形状と利用可能なインデックスの両方に依存します。クエリ形状をサポートするインデックスを追加または削除すると、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 番目のインデックスはクエリ 2 をサポートしますが、クエリ 1 はサポートしないため、この 2 つのクエリは異なるプランキャッシュキーを持ちます。インデックス {x:1, a:1} を追加すると、両方のキャッシュキーが更新されます。
クエリ再計画
コレクションのデータが変化すると、キャッシュされたクエリプランが非最適になり、適応が必要となる場合があります。
クエリがキャッシュ済みのプランに一致した場合、プランナーはそのプランを直接再利用しながらパフォーマンスをモニタリングします。キャッシュされたプランが代替案よりも著しく非効率的になった場合(たとえば、10 倍以上遅くなった場合)、プランナーは実行を中止し、そのプランをキャッシュから削除して、すべての候補プランを再度評価します。これをクエリ再計画と呼びます。
影響とソリューション
スロークエリログにキーワード "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"影響
クエリパフォーマンスが低下します。
ミューテックスロックの競合および CPU 使用率の急上昇を引き起こします。
ソリューション
一時的にインスタンススペックをアップグレードして、データベースの負荷を軽減します。
プランキャッシュをクリアし、プランナーがより良いプランを選択するかどうかを確認します。
再計画を引き起こすクエリ条件に対して、アプリケーション内で
hint()を使用してインデックスを指定します。db.<collection>.find({a:"ABC"},{b:1,_id:0}).sort({c:1}).hint({ a: 1, c: 1, b: 1} )説明ヒントワードは、クエリプランナーのデフォルト動作をオーバーライドします。
再計画をトリガーするクエリに対して、インデックスフィルターを使用して利用可能なインデックスを制限します。
// インデックスが存在するか確認 db.<collection>.getIndexes() // インデックスフィルターを設定 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>" } )説明インデックスフィルターは、クエリプランナーのデフォルト動作をオーバーライドします。
クエリにヒントワードとインデックスフィルターの両方が指定されている場合、インデックスフィルターが優先されます。インデックスフィルターは慎重に使用してください。インデックスフィルター。
クエリオプティマイザーは、コレクションスキャン(COLLSCAN)または
planCacheSetFilterで指定されたインデックスのいずれかを使用します。指定されたインデックスが存在しない、または非表示になっている場合、オプティマイザーはコレクションスキャンにフォールバックし、これにより CPU 使用率および I/O の急上昇を引き起こす可能性があります。planCacheSetFilterを実行する前に、db.<collection>.getIndexes()を使用してインデックスが存在することを確認してください。planCacheSetFilter。
(推奨)再計画を防止するために、クエリおよびインデックスを最適化します。
説明ヒントワードやインデックスフィルターは回避策です。クエリ、インデックス、ドキュメントスキーマを見直すことで、通常はより良い結果が得られます。
(推奨)メジャーバージョン 4.2 または 4.4 を実行中のインスタンスについては、最新のカーネルマイナーバージョンに更新してミューテックスロックの競合を軽減する(SERVER-40805)、またはメジャーバージョンを 5.0 または 6.0 にアップグレードします。データベースのマイナーバージョンをアップグレードする。データベースのメジャーバージョンをアップグレードする。
これらのソリューションで問題が解決しない場合は、チケットを送信してテクニカルサポートをご利用ください。