【MongoDB】実行計画を確認する

実行計画とは?

DBクエリには実行計画というものがあります。ざっくりとした説明します。実行計画は名前からも予測できるかもしれませんが、クエリが実行される前に作成されるものでSQLのパフォーマンスの情報などが載っているものです。

そのため、実行計画はクエリチューニングのパフォーマンス計測などに使われます。ちなみに実行計画は実際にSQLを実行しなくても確認することが可能です。

Mongoクエリの実行計画を取得する

以下の記事同様でデータベース名やコレクション名は私が現在使用しているものですので、適宜読み変えてください。

https://it-blue-collar-dairy.com/add-index_for_mongodb/

MongoDBで実行計画を取得するのは非常に簡単です。shellの最後に.explain()をつけるだけです。

以下のクエリは「date > 20190210」を条件に、データを取得しています。dateにはインデックスを貼っています。

> db.trade_data.find({"date": {"$gt": "20190210"}}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "trade_database.trade_data",
		"indexFilterSet" : false,
		"parsedQuery" : {

		},
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "***-**-**-***",
		"port" : 27017,
		"version" : "4.0.4",
		"gitVersion" : "f288a3bdf201007f3693c58e140056adf8b04839"
	},
	"ok" : 1
}

と思っていたのですが、上記では何故か実行計画の詳細が出ていません…。調べてみたらexplain()に引数が必要とのことでした。MongoDB v.3.0から変更されていたようです。

以下のようにすると実行計画の詳細が取得できます。

> db.trade_data.find({"date": {"$gt": "20190210"}}).explain("executionStats")
{
       "queryPlanner" : {
               # 上記と内容が同じのため省略
       },
       "executionStats" : {
               "executionSuccess" : true,
               "nReturned" : 1447, # 取得するデータ数
               "executionTimeMillis" : 216, # 実行時間
               "totalKeysExamined" : 1447, # インデックスでの走査数
               "totalDocsExamined" : 1447, # 走査したドキュメント数
               "executionStages" : {
                       "stage" : "FETCH",
                       "nReturned" : 1447,
                       "executionTimeMillisEstimate" : 210,
                       "works" : 1448,
                       "advanced" : 1447,
                       "needTime" : 0,
                       "needYield" : 0,
                       "saveState" : 12,
                       "restoreState" : 12,
                       "isEOF" : 1,
                       "invalidates" : 0,
                       "docsExamined" : 1447,
                       "alreadyHasObj" : 0,
                       "inputStage" : {
                               "stage" : "IXSCAN",
                               "nReturned" : 1447,
                               "executionTimeMillisEstimate" : 0,
                               "works" : 1448,
                               "advanced" : 1447,
                               "needTime" : 0,
                               "needYield" : 0,
                               "saveState" : 12,
                               "restoreState" : 12,
                               "isEOF" : 1,
                               "invalidates" : 0,
                               "keyPattern" : {
                                       "date" : 1
                               },
                               "indexName" : "date_1",
                               "isMultiKey" : false,
                               "multiKeyPaths" : {
                                       "date" : [ ]
                               },
                               "isUnique" : false,
                               "isSparse" : false,
                               "isPartial" : false,
                               "indexVersion" : 2,
                               "direction" : "forward",
                               "indexBounds" : {
                                       "date" : [
                                               "(\"20190210\", {})"
                                       ]
                               },
                               "keysExamined" : 1447,
                               "seeks" : 1,
                               "dupsTested" : 0,
                               "dupsDropped" : 0,
                               "seenInvalidated" : 0
                       }
               }
       },
       "serverInfo" : {
              # 省略
       },
       "ok" : 1
}

確認するべき箇所は上記でコメントをいれていますが、以下の項目が重要です。

executionStats.nReturned : 取得するデータ数
executionStats.executionTimeMillis : 実行時間
executionStats.totalKeysExamined : インデックスでの走査数
executionStats.totalDocsExamined : 走査したドキュメント数

また、executionStages.stageには走査対象が表示されます。これがCOLLSCANになっていたりすると、全てのデータを操作しているため、パフォーマンス上よくありません。

インデックスを削除して実行計画を取得してみる

上記のクエリをdateのインデックスを削除した状態で実行すると以下のようになります。

> db.trade_data.find({"date": {"$gt": "20190210"}}).explain("executionStats")
{
       "queryPlanner" : {
              # 省略
       },
       "executionStats" : {
               "executionSuccess" : true,
               "nReturned" : 1447,
               "executionTimeMillis" : 2120,
               "totalKeysExamined" : 0,
               "totalDocsExamined" : 12305,
               "executionStages" : {
                       "stage" : "COLLSCAN", # 全カラムが取得されるようになっている
                       "filter" : {
                               "date" : {
                                       "$gt" : "20190210"
                               }
                       },
                       "nReturned" : 1447,
                       "executionTimeMillisEstimate" : 2109,
                       "works" : 12307,
                       "advanced" : 1447,
                       "needTime" : 10859,
                       "needYield" : 0,
                       "saveState" : 116,
                       "restoreState" : 116,
                       "isEOF" : 1,
                       "invalidates" : 0,
                       "direction" : "forward",
                       "docsExamined" : 12305
               }
       },
       "serverInfo" : {
               # 省略
       },
}

executionStages.stageがCOLLSCANになり、走査したデータ数が増え、実行時間も増えています。

さいごに

実行計画はパフォーマンス計測を簡単に行うことができます。実際の業務ではツールなどを使った測定を行うと思うのですが、さっと試したいときには使用できるのでぜひ使ってみてください。

また、今回はMongoDBでしたが、MySQLやOracleなどでも同様に実行計画はあります。