MongoDBクエリ分析と実行計画の解読

MongoDBクエリ分析と実行計画の解読

MongoDBのクエリ分析は、作成したインデックスの有効性を確認し、クエリのパフォーマンスを最適化するための重要なツールです。主にexplain()hint()という関数が使用されます。

explain()の利用

explain()操作は、クエリ情報、インデックスの使用状況、クエリ統計などを提供し、インデックスの最適化に役立ちます。まず、productsコレクションにcategoryproduct_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()を使用してパフォーマンスを向上させることができます。

以下の例では、categoryproduct_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のサブプロセスです。

タグ: MongoDB クエリ分析 インデックス パフォーマンスチューニング

6月2日 20:28 投稿