Amazon DocumentDBは使っていますか?
Amazon DocumentDBはMongoDBの互換のドキュメント指向データベースであり、サービスの概要がわかるBlackBeltの資料は2019年のものが最新となっている。
MongoDBの互換となっているもののサポートされている MongoDB API、オペレーション、およびデータ型を見ると様々な面で機能差異があることがわかる。
特にマルチバイトを取り扱い場合においてはcollationに対応していないなどの問題があるため、検索要件が一致しているかどうかを検討する必要がある。
私が検証した中で、ドキュメントに記載がされていない部分において、マルチバイト検索においてインデックス適切に利用されず検索性能が大幅に低下する問題に遭遇したので、回避方法ともに紹介する。
今回の検証は、DocumentDB 4.0で行っているが、5.0でも事象が変わらない。
key に対して単一キーのインデックスを事前に作成する
rs0 [direct: primary] db1> db.col.createIndex({scope:1, key: 1})
scope_1_key_1
その上で、半角文字で開始するコレクションを検索すると4133レコードの結果がある場合でも134 msと高速に応答する。
rs0:PRIMARY> db.col.find({ scope:"xxxxx", key: /^A/ }).hint({scope:1,key:1}).explain("executionStats");
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db1.col", "winningPlan" : { "stage" : "SUBSCAN", "inputStage" : { "stage" : "SORT", "sortPattern" : { "key" : 1, "scope" : 1 }, "inputStage" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXOR", "inputStages" : [ { "stage" : "IXSCAN", "indexName" : "scope_1_key_1" }, { "stage" : "IXSCAN", "indexName" : "scope_1_key_1" } ] } } } } }, "executionStats" : { "executionSuccess" : true, "executionTimeMillis" : "134.866", "planningTimeMillis" : "0.632", "executionStages" : { "stage" : "SUBSCAN", "nReturned" : "4133", "executionTimeMillisEstimate" : "133.853", "inputStage" : { "stage" : "SORT", "nReturned" : "4133", "executionTimeMillisEstimate" : "132.697", "sortPattern" : { "key" : 1, "scope" : 1 }, "inputStage" : { "stage" : "FETCH", "nReturned" : "4133", "executionTimeMillisEstimate" : "105.506", "inputStage" : { "stage" : "IXOR", "nReturned" : "0", "executionTimeMillisEstimate" : "34.238", "inputStages" : [ { "stage" : "IXSCAN", "nReturned" : "4133", "executionTimeMillisEstimate" : "33.333", "indexName" : "scope_1_key_1" }, { "stage" : "IXSCAN", "nReturned" : "0", "executionTimeMillisEstimate" : "0.902", "indexName" : "scope_1_key_1" } ] } } } } }, "serverInfo" : { "host" : "xxxxxxx", "port" : 27017, "version" : "4.0.0" }, "ok" : 1, "operationTime" : Timestamp(1703472746, 1)
}
しかしながら、全角文字で開始するコレクションの検索を行った場合、先ほどの条件に対して2.72倍の11266レコードの結果がある場合に、応答時間が153倍の 20518 msもかかる。
rs0:PRIMARY> db.col.find({ scope:"xxxxx", key: /^あ/ }).hint({scope:1,key:1}).explain("executionStats");
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db1.col", "winningPlan" : { "stage" : "SUBSCAN", "inputStage" : { "stage" : "IXSCAN", "indexName" : "scope_1_key_1", "direction" : "forward" } } }, "executionStats" : { "executionSuccess" : true, "executionTimeMillis" : "20518.276", "planningTimeMillis" : "0.303", "executionStages" : { "stage" : "SUBSCAN", "nReturned" : "11266", "executionTimeMillisEstimate" : "20517.382", "inputStage" : { "stage" : "IXSCAN", "nReturned" : "11266", "executionTimeMillisEstimate" : "20514.086", "indexName" : "scope_1_key_1", "direction" : "forward" } } }, "serverInfo" : { "host" : "xxxxxxx", "port" : 27017, "version" : "4.0.0" }, "ok" : 1, "operationTime" : Timestamp(1703472782, 1)
}
対応方法としては、正規表現を利用せずに文字コード範囲の検索によって実現することで応答時間を早めることができる。
db.col.find({ scope:”xxxxx”, key: {$gte: “あ”, $lt: String.fromCharCode(“あ”.charCodeAt(0)+1) } })
rs0:PRIMARY> db.col.find({ scope:"xxxxx", key: {$gte: "あ", $lt: "ぃ"}}).hint({scope:1,key:1}).explain("executionStats");
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db1.col", "winningPlan" : { "stage" : "SUBSCAN", "inputStage" : { "stage" : "IXSCAN", "indexName" : "scope_1_key_1", "direction" : "forward" } } }, "executionStats" : { "executionSuccess" : true, "executionTimeMillis" : "367.233", "planningTimeMillis" : "0.499", "executionStages" : { "stage" : "SUBSCAN", "nReturned" : "11266", "executionTimeMillisEstimate" : "366.099", "inputStage" : { "stage" : "IXSCAN", "nReturned" : "11266", "executionTimeMillisEstimate" : "362.641", "indexName" : "scope_1_key_1", "direction" : "forward" } } }, "serverInfo" : { "host" : "xxxxxxx", "port" : 27017, "version" : "4.0.0" }, "ok" : 1, "operationTime" : Timestamp(1703472821, 1)
}