MongoDBクエリ分析と実行計画の解読
MongoDBのクエリ分析は、作成したインデックスの有効性を確認し、クエリのパフォーマンスを最適化するための重要なツールです。主にexplain()とhint()という関数が使用されます。
explain()の利用
explain()操作は、クエリ情報、インデックスの使用状況、クエリ統計などを提供し、インデックスの最適化に役立ちます。まず、productsコレクションにcategoryとproduct_nameの複合インデックスを作成します。
db.products.createIndex({category: 1, product_name: 1})
次に、クエリでexplain()を使用して、その実行計画を分析します。
db.products.find({category: "electronics"}, {product_name: 1, _id: 0}).explain("executionStats")
上記のexplain()クエリは、以下のような結果を返します。
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.products",
"indexFilterSet" : false,
"parsedQuery" : {
"category" : {
"$eq" : "electronics"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"category" : 1,
"product_name" : 1
},
"indexName" : "category_1_product_name_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"category" : [
"["electronics", "electronics"]"
],
"product_name" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 150,
"executionTimeMillis" : 5,
"totalKeysExamined" : 150,
"totalDocsExamined" : 150,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 150,
"executionTimeMillisEstimate" : 4,
"works" : 151,
"advanced" : 150,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 150,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 150,
"executionTimeMillisEstimate" : 1,
"works" : 151,
"advanced" : 150,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"category" : 1,
"product_name" : 1
},
"indexName" : "category_1_product_name_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"category" : [
"["electronics", "electronics"]"
],
"product_name" : [
"[MinKey, MaxKey]"
]
},
"keysExamined" : 150,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "localhost",
"port" : 27017,
"version" : "5.0.5",
"gitVersion" : "65f0fc51c6c6c1050560cc0570fb8d68a7f1ac637"
},
"ok" : 1
}
この結果セットの主要なフィールドを解説します。
- queryPlanner: クエリの実行計画に関する情報を含みます。
- queryPlanner.winningPlan: 最適と判断された実行計画です。
- executionStats: クエリの実行に関する統計情報です。
- executionStats.nReturned: クエリによって返されたドキュメントの数です。
- executionStats.executionTimeMillis: クエリの実行にかかった時間(ミリ秒単位)です。
- executionStats.totalKeysExamined: スキャンされたインデックスエントリの総数です。
- executionStats.totalDocsExamined: スキャンされたドキュメントの総数です。
hint()の利用
MongoDBのクエリオプティマイザは通常、最適なインデックスを選択しますが、特定のインデックスを強制的に使用させたい場合があります。そのような状況では、hint()を使用してパフォーマンスを向上させることができます。
以下の例では、categoryとproduct_nameのインデックスを使用するように指定しています。
db.products.find({category: "electronics"}, {product_name: 1, _id: 0}).hint({category: 1, product_name: 1})
このクエリの実行計画をexplain()で分析することもできます。
db.products.find({category: "electronics"}, {product_name: 1, _id: 0}).hint({category: 1, product_name: 1}).explain("executionStats")
MongoDBクエリ操作の分析
背景
MongoDBはSQLライクなデータクエリと操作を提供し、集計操作やインデックスなどの機能も備えています。経験上、不適切なデータベース操作やインデックス設計は、クエリの遅延、データベースのスループットの低下、CPUやディスクI/Oの急増などの問題を引き起こすことがあります。したがって、アプリケーション開発プロセスにおいて、特に重要なビジネスロジックや複雑な条件クエリについては、データベース操作をレビューし、潜在的なボトルネックを事前に分析することが重要です。MongoDBはexplainメソッドを提供しており、これを使用してデータベースクエリ文を分析し、パフォーマンスの問題を特定できます。
クエリ計画
MongoDBは、クエリ計画(QueryPlan)を使用してクエリ文の実行プロセスを記述します。通常、1つのクエリ操作には複数のクエリ計画が対応する場合があります。これらの計画は選挙メカニズムを通じて最適な計画が選ばれ、最終的な実行方案となります。さらに、MongoDBはクエリ計画のキャッシュメカニズムも提供しています。クエリ操作はクエリモデル(query shape)にマッピングされ、モデルには条件(predicate)、ソート(sort)、投影(projection)の定義が含まれます。このクエリモデルをキーとして既存の計画キャッシュを検索します。キャッシュが見つかった場合でも、パフォーマンス評価が基準を満たさない場合は、MongoDBはキャッシュを破棄し、クエリ計画の生成段階に進みます。各計画生成段階には以下のステップが含まれます。
- 候補計画の生成
- 優れた計画の評価
- 最適計画の選出
- キャッシュの作成
最適な計画が生成された後、クエリエグゼキュータがその計画を実行し、最終結果を生成します。
explain操作
以下のステートメントを使用して、現在のクエリ計画を分析できます。
db.orders.find({
"customerId": "cust123",
"status": "shipped",
"orderDate": {
$gte: ISODate("2023-01-01T00:00:00Z"),
$lt: ISODate("2023-12-31T23:59:59Z")
}
}).explain("executionStats")
出力結果は以下のようになります。
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.orders",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"customerId" : {
"$eq" : "cust123"
}
},
{
"status" : {
"$eq" : "shipped"
}
},
{
"orderDate" : {
"$lt" : ISODate("2023-12-31T23:59:59Z")
}
},
{
"orderDate" : {
"$gte" : ISODate("2023-01-01T00:00:00Z")
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"customerId" : 1,
"orderDate" : 1
},
"indexName" : "customerId_1_orderDate_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"customerId" : [
"["cust123", "cust123"]"
],
"orderDate" : [
"[new Date(1672531200000), new Date(1704067199000)]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 45,
"executionTimeMillis" : 12,
"totalKeysExamined" : 45,
"totalDocsExamined" : 45,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 45,
"executionTimeMillisEstimate" : 10,
"works" : 46,
"advanced" : 45,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 45,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 45,
"executionTimeMillisEstimate" : 2,
"works" : 46,
"advanced" : 45,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"customerId" : 1,
"orderDate" : 1
},
"indexName" : "customerId_1_orderDate_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"customerId" : [
"["cust123", "cust123"]"
],
"orderDate" : [
"[new Date(1672531200000), new Date(1704067199000)]"
]
},
"keysExamined" : 45,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "localhost",
"port" : 27017,
"version" : "5.0.5",
"gitVersion" : "65f0fc51c6c6c1050560cc0570fb8d68a7f1ac637"
},
"ok" : 1
}
結果の説明
| 属性 | 説明 |
|---|---|
| queryPlanner | 現在のクエリ計画を記述します。 |
| queryPlanner.namespace | 現在のコレクションの名前空間を記述します({db}.{collectionName})。 |
| queryPlanner.indexFilterSet | indexFilterが設定されているかどうかを示します。Filterは、クエリオプティマイザが特定のクエリでインデックスを使用する方法を決定します。 |
| queryPlanner.parsedQuery | 解析されたクエリ情報です。 |
| queryPlanner.winningPlan | 最適な計画です。 |
| queryPlanner.rejectPlans | 拒否された計画のリストです。 |
| executionStats | 実行プロセスの統計情報をキャプチャし、計画の実行中の関連情報を提供します。 |
| executionStats.executionSuccess | 実行が成功したかどうかを示します。 |
| executionStats.nReturned | 返されたアイテムの数です。 |
| executionStats.executionTimeMillis | 実行時間(ミリ秒)です。 |
| executionStats.totalKeysExamined | スキャンされたインデックスエントリの総数です。 |
| executionStats.totalDocsExamined | スキャンされたドキュメントの総数です。 |
| executionStats.executionStages | 実行ステージの詳細です。 |
explainモード
MongoDBは、explain操作に対していくつかのモードを提供しています。
- queryPlanner: デフォルトのモードで、クエリ計画の分析のみを行い、実行プロセスの統計情報は出力されません。
- executionStats: クエリ計画の分析後、
winningPlanを実行し、プロセス情報を統計します。 - allPlansExecution: すべての計画(
rejectedPlansを含む)を実行し、プロセス統計情報を返します。
実行計画の詳細解説
実行計画は、プロセス全体を複数のステージ(stage)に分解し、これらのステージはツリー構造で組織されています。ステージには多种のタイプがあります。
| ステージ | 説明 |
|---|---|
| COLLSCAN | コレクションスキャン(全表スキャン) |
| IXSCAN | インデックススキャン |
| FETCH | インデックスに基づいて指定されたドキュメントを取得します。 |
| PROJECTION | 返されるフィールドを制限します。 |
| SHARD_MERGE | 各シャードから返されたデータをマージします。 |
| SORT | メモリ内でソートが行われたことを示します。 |
| LIMIT | limitを使用して返される数を制限します。 |
| SKIP | skipを使用してスキップします。 |
| IDHACK | _idに対するクエリです。 |
| SHARDING_FILTER | mongosを通じてシャードデータをクエリします。 |
| COUNT | db.coll.explain().count()などのcount演算子を使用します。 |
| COUNTSCAN | インデックスを使用せずにcountを実行します。 |
| COUNT_SCAN | インデックスを使用してcountを実行します。 |
| SUBPLA | インデックスが使用されていない$orクエリです。 |
| TEXT | 全文インデックスを使用してクエリします。 |
winningPlanの例
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"$and" : [
{
"status" : {
"$eq" : "shipped"
}
},
{
"orderDate" : {
"$lt" : ISODate("2023-12-31T23:59:59Z")
}
},
{
"orderDate" : {
"$gte" : ISODate("2023-01-01T00:00:00Z")
}
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"customerId" : 1,
"orderDate" : 1
},
"indexName" : "customerId_1_orderDate_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"customerId" : [
"["cust123", "cust123"]"
],
"orderDate" : [
"[new Date(1672531200000), new Date(1704067199000)]"
]
}
}
}
フィールドの説明
| 属性 | 説明 |
|---|---|
| winningPlan.stage | 最適な計画のステージです。FETCHは、インデックスに基づいてドキュメントを取得することを意味します。 |
| winningPlan.filter | 最適な計画のフィルタ、つまりクエリ条件です。 |
| winningPlan.inputStage | 最適な計画のステージの子ステージです。 |
| winningPlan.inputStage.stage | 子ステージです。ここではIXSCANで、インデックススキャンが行われていることを示します。 |
| winningPlan.inputStage.keyPattern | スキャンされたインデックスのパターンです。 |
| winningPlan.inputStage.indexName | 使用されるインデックスの名前です。 |
| winningPlan.inputStage.isMultiKey | Multikeyかどうか。インデックスが配列に基づいている場合はtrueです。 |
| winningPlan.inputStage.isSparse | 疎インデックスかどうかです。 |
| winningPlan.inputStage.isPartial | 部分インデックスかどうかです。 |
| winningPlan.inputStage.direction | このクエリのクエリ順序です。ここではforwardです。もし.sort({orderDate: -1})を使用した場合はbackwardが表示されます。 |
| winningPlan.inputStage.indexBounds | スキャンされたインデックスの範囲です。 |
プロセス統計の詳細解説
executionStatsの例
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 45,
"executionTimeMillis" : 12,
"totalKeysExamined" : 45,
"totalDocsExamined" : 45,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"$and" : [
{
"status" : {
"$eq" : "shipped"
}
},
{
"orderDate" : {
"$lt" : ISODate("2023-12-31T23:59:59Z")
}
},
{
"orderDate" : {
"$gte" : ISODate("2023-01-01T00:00:00Z")
}
}
]
},
"nReturned" : 45,
"executionTimeMillisEstimate" : 10,
"works" : 46,
"advanced" : 45,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 45,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 45,
"executionTimeMillisEstimate" : 2,
"works" : 46,
"advanced" : 45,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"customerId" : 1,
"orderDate" : 1
},
"indexName" : "customerId_1_orderDate_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"customerId" : [
"["cust123", "cust123"]"
],
"orderDate" : [
"[new Date(1672531200000), new Date(1704067199000)]"
]
},
"keysExamined" : 45,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
フィールドの説明
| 属性 | 説明 |
|---|---|
| executionStats.executionSuccess | 実行が成功したかどうかを示します。 |
| executionStats.nReturned | 返されたアイテムの数です。 |
| executionStats.executionTimeMillis | 実行時間(ミリ秒)です。 |
| executionStats.totalKeysExamined | スキャンされたインデックスエントリの総数です。 |
| executionStats.totalDocsExamined | スキャンされたドキュメントの総数です。 |
| executionStats.executionStages | 実行ステージの詳細です。ほとんどのフィールドはwinningPlan.inputStageから継承されます。 |
| executionStats.executionStages.stage | 実行ステージです。FETCHは、インデックスに基づいてドキュメントを取得することを意味します。 |
| executionStats.executionStages.nReturned | ステージによって返されたアイテムの数です。 |
| executionStats.executionStages.executionTimeMillisEstimate | ステージの実行時間です。 |
| executionStats.executionStages.docsExamined | ステージでスキャンされたドキュメントの数です。 |
| executionStats.executionStages.works | ステージで実行されたタスクの数です。 |
| executionStats.executionStages.advanced | ステージでアップストリームに渡されたアイテムの数です。 |
| executionStats.executionStages.needTime | ステージでインデックスの位置を特定するために必要な時間です。 |
| executionStats.executionStages.needYield | ステージでロックを取得するために待機した時間です。 |
| executionStats.executionStages.isEOF | ステージでストリームの終わりに達したかどうかを示します。limit制限子付きのクエリでは0になる可能性があります。 |
| executionStats.executionStages.inputStage | 実行ステージのサブステージです。ここではIXSCANのサブプロセスです。 |