[MongoDB 4.2 新機能] Wildcard Index を使ってみる

こんにちは、菊池です。

MongoDBの次期バージョンである、MognoDB 4.2の新機能を紹介していきます。今回は個人的に目玉の1つである Wildcard Index です。

検証環境では、以下の記事でインストールしたMongoDB 4.2RCを利用していきます。

MongoDB 最新バージョン 4.2 RC版を Amazon Linux 2 にインストールする

Wildcard Index

MongoDBでは、高速なクエリレスポンスを実現するためには、よく使う検索条件に対してはインデックスを作成しておくのが必須です。しかし、更新のパフォーマンスを考慮すると、インデックスは最小限にしておきたいところです。また、柔軟なデータ構造が売りの1つでもあるMongoDBでは、データ(ドキュメント)によってその項目の有無が異なることも多いです。

以下のようなドキュメントを例にしていきます。

{
  "name" : "John",
  "userMetadata" : {
    "likes" : [ "dogs", "cats" ],
    "dislikes" : "pickles",
    "age" : 45
  }
}

userMetadataの各項目に対して検索の要件がある場合、従来のバージョンでは以下のようにそれぞれの項目に対してインデックスの作成が必要でした。

db.user.createIndex(
  { 
    "userMetadata.likes" : 1,
    "userMetadata.dislikes" : 1,
    "userMetadata.age" : 1
  }
)

それが、4.2からサポートされるWildcard Indexでは、以下の1つのインデックスで任意のキーに対して有効になります。

db.user.createIndex( { "userMetadata.$**" : 1 } )

制約事項

非常に便利に見えるWildcard Indexですが、いくつか制約があることに注意が必要です。

  • 非対応のインデックスタイプ
    • Compound
    • TTL
    • Text
    • 2d (Geospatial)
    • 2dsphere (Geospatial)
    • Hashed
    • Unique
  • 一部クエリ、Aggregationに非対応(インデックスが有効にならない)
  • シャードキーとして使用不可

詳細は公式ドキュメントを参照ください。

Wildcard Indexを試す

実際に試してみます。まずはドキュメントを作成。

> db.user.insert(
... {
...   "name" : "John",
...   "userMetadata" : {
...     "likes" : [ "dogs", "cats" ],
...     "dislikes" : "pickles",
...     "age" : 45
...   }
... }
... )
WriteResult({ "nInserted" : 1 })

コレクションにWildcard Indexを作成します。

> db.user.createIndex( { "userMetadata.$**" : 1 } )
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}

作成されたインデックスがちゃんと有効になっているか、実行計画を確認してみます。userMetadata.likesに対してクエリを実行すると。"stage" : "IXSCAN"(インデックススキャン)なので、インデックスを使用していることがわかります。

> db.user.find({"userMetadata.likes": "dogs"}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "localhost.user",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"userMetadata.likes" : {
				"$eq" : "dogs"
			}
		},
		"queryHash" : "AD6DC2A1",
		"planCacheKey" : "91099FFF",
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"$_path" : 1,
					"userMetadata.likes" : 1
				},
				"indexName" : "userMetadata.$**_1",
				"isMultiKey" : true,
				"multiKeyPaths" : {
					"$_path" : [ ],
					"userMetadata.likes" : [
						"userMetadata.likes"
					]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"$_path" : [
						"[\"userMetadata.likes\", \"userMetadata.likes\"]"
					],
					"userMetadata.likes" : [
						"[\"dogs\", \"dogs\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "ip-172-31-27-251.ap-northeast-1.compute.internal",
		"port" : 27017,
		"version" : "4.2.0-rc2",
		"gitVersion" : "de6592f457144d3391e108f0d9fc41436a3655e9"
	},
	"ok" : 1
}

一方で、インデックスを付与していないキーへのクエリは、"stage" : "COLLSCAN"と、コレクションへのスキャンが実行されます。

> db.user.find({"name": "John"}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "localhost.user",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"name" : {
				"$eq" : "John"
			}
		},
		"queryHash" : "01AEE5EC",
		"planCacheKey" : "01AEE5EC",
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"name" : {
					"$eq" : "John"
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "ip-172-31-27-251.ap-northeast-1.compute.internal",
		"port" : 27017,
		"version" : "4.2.0-rc2",
		"gitVersion" : "de6592f457144d3391e108f0d9fc41436a3655e9"
	},
	"ok" : 1
}

また、Wildcard Indexが有効にならない、$exists条件でのクエリでは、以下のようにインデックスを使用してくれません。

> db.user.find( {"userMetadata.dislikes" : { $exists : false } } ).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "localhost.user",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"userMetadata.dislikes" : {
				"$not" : {
					"$exists" : true
				}
			}
		},
		"queryHash" : "1668A6FE",
		"planCacheKey" : "6AA40C11",
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"userMetadata.dislikes" : {
					"$not" : {
						"$exists" : true
					}
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "ip-172-31-27-251.ap-northeast-1.compute.internal",
		"port" : 27017,
		"version" : "4.2.0-rc2",
		"gitVersion" : "de6592f457144d3391e108f0d9fc41436a3655e9"
	},
	"ok" : 1
}

さいごに

MongoDB次期バージョンである4.2の新機能、Wildcard Indexを紹介しました。クエリ/集計によっては使えない(Indexを使わない)という制約には要注意ですが、うまく使えばIndex量の削減に役立つ機能となるでしょう。