背景として、私の質問に最も関連していると思われるドキュメントのページは、マルチキー インデックスの境界とインデックスの交差に関するこのページです。
サブドキュメントの配列内のフィールドに対する $elemMatch クエリで、mongo がインデックス境界を適切に結合していないように見えるという問題があります。
ドキュメントは、私の特定のユースケースが機能しないとは言っていないようで、私がやっていることに非常に近い例がたくさんあります。技術的な観点から、これが機能しない理由は思いつきません。私は何かを見逃していましたか、それともなぜこれがそのように振る舞うのか誰かが私に説明できますか?
次のようなドキュメントのコレクションから始めています。
mongos> db.test.findOne()
{
"_id" : ObjectId("54c7fdaa9a9950e75fa616b9"),
"data" : [
{
"point" : 1,
"other" : "what"
}
]
}
次のようなインデックスを配置しています。
mongos> db.test.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "temp.test"
},
{
"v" : 1,
"key" : {
"data.point" : 1
},
"name" : "data.point_1",
"ns" : "temp.test"
}
]
次のように、データ配列にサブドキュメントが 1 つしかない場合:
mongos> db.test.find()
{ "_id" : ObjectId("54c7fdaa9a9950e75fa616b9"), "data" : [ { "point" : 1, "other" : "what" } ] }
{ "_id" : ObjectId("54c7fdaf9a9950e75fa616ba"), "data" : [ { "point" : 2, "other" : "who" } ] }
{ "_id" : ObjectId("54c7fdb59a9950e75fa616bb"), "data" : [ { "point" : 3, "other" : "where" } ] }
データに対する $elemMatch クエリは問題なく動作します。
mongos> db.test.find({data: {$elemMatch: {point: {$gte: 2, $lte: 2}}}})
{ "_id" : ObjectId("54c7fdaf9a9950e75fa616ba"), "data" : [ { "point" : 2, "other" : "who" } ] }
mongos> db.test.find({data: {$elemMatch: {point: {$gte: 2, $lte: 2}}}}).explain(true)
{
"cursor" : "BtreeCursor data.point_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"data.point" : [
[
2,
2
]
]
},
"allPlans" : [
{
"cursor" : "BtreeCursor data.point_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"data.point" : [
[
2,
2
]
]
}
}
],
"server" : "XXXXXX",
"filterSet" : false,
"stats" : {
"type" : "KEEP_MUTATIONS",
"works" : 2,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"children" : [
{
"type" : "FETCH",
"works" : 2,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"alreadyHasObj" : 0,
"forcedFetches" : 0,
"matchTested" : 1,
"children" : [
{
"type" : "IXSCAN",
"works" : 2,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"keyPattern" : "{ data.point: 1.0 }",
"isMultiKey" : 0,
"boundsVerbose" : "field #0['data.point']: [2.0, 2.0]",
"yieldMovedCursor" : 0,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0,
"matchTested" : 0,
"keysExamined" : 1,
"children" : [ ]
}
]
}
]
},
"millis" : 0
}
data
しかし、配列内に複数のサブドキュメントを含むドキュメントを追加すると、次のようになります。
mongos> db.test.insert({data: [{point: 3, other: 'where'}, {point:4, other:"huh"}]})
WriteResult({ "nInserted" : 1 })
mongos> db.test.find()
{ "_id" : ObjectId("54c7fdaa9a9950e75fa616b9"), "data" : [ { "point" : 1, "other" : "what" } ] }
{ "_id" : ObjectId("54c7fdaf9a9950e75fa616ba"), "data" : [ { "point" : 2, "other" : "who" } ] }
{ "_id" : ObjectId("54c7fdb59a9950e75fa616bb"), "data" : [ { "point" : 3, "other" : "where" } ] }
{ "_id" : ObjectId("54c806c39a9950e75fa616bc"), "data" : [ { "point" : 3, "other" : "where" }, { "point" : 4, "other" : "huh" } ] }
クエリは桁違いに長くかかり (自明でないテスト ケースでは)、Explain は境界を適切な から に変更し[2, 2]
、[-Infinity, 2]
フラグisMultiKey
はに変わりtrue
ます。
mongos> db.test.find({data: {$elemMatch: {point: {$gte: 2, $lte: 2}}}}).explain(true)
{
"cursor" : "BtreeCursor data.point_1",
"isMultiKey" : true,
"n" : 1,
"nscannedObjects" : 2,
"nscanned" : 2,
"nscannedObjectsAllPlans" : 2,
"nscannedAllPlans" : 2,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"data.point" : [
[
-Infinity,
2
]
]
},
"allPlans" : [
{
"cursor" : "BtreeCursor data.point_1",
"isMultiKey" : true,
"n" : 1,
"nscannedObjects" : 2,
"nscanned" : 2,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"data.point" : [
[
-Infinity,
2
]
]
}
}
],
"server" : "XXXXXX",
"filterSet" : false,
"stats" : {
"type" : "KEEP_MUTATIONS",
"works" : 3,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 1,
"needFetch" : 0,
"isEOF" : 1,
"children" : [
{
"type" : "FETCH",
"works" : 3,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 1,
"needFetch" : 0,
"isEOF" : 1,
"alreadyHasObj" : 0,
"forcedFetches" : 0,
"matchTested" : 1,
"children" : [
{
"type" : "IXSCAN",
"works" : 3,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 2,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"keyPattern" : "{ data.point: 1.0 }",
"isMultiKey" : 1,
"boundsVerbose" : "field #0['data.point']: [-inf.0, 2.0]",
"yieldMovedCursor" : 0,
"dupsTested" : 2,
"dupsDropped" : 0,
"seenInvalidated" : 0,
"matchTested" : 0,
"keysExamined" : 2,
"children" : [ ]
}
]
}
]
},
"millis" : 0
}
正しい btree インデックスを使用して正しい結果を取得していますが、この問題は、大規模なデータセットで使用できるかどうかの違いです。
私が使用しているクエリもこれと同等であることを知っています:
db.test.find({data: {$elemMatch: {point: 2}}})
しかし、私は簡単にするためにそれをやっています - $gt $gte $lt $lte のいずれかで境界を指定すると、インデックス境界がインデックスで不適切に設定されているという点で同じ動作が観察されます (または私にはそう思われます)。
参考までに、上記のクエリを実行すると、実際に予想されるインデックス境界が取得されるため、mongo が複数のサブドキュメントの配列を使用して必要な計画を満たすクエリを発行できないわけではありません。
{
"cursor" : "BtreeCursor data.point_1",
"isMultiKey" : true,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"data.point" : [
[
2,
2
]
]
},
"allPlans" : [
{
"cursor" : "BtreeCursor data.point_1",
"isMultiKey" : true,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"data.point" : [
[
2,
2
]
]
}
}
],
"server" : "XXXXXX",
"filterSet" : false,
"stats" : {
"type" : "KEEP_MUTATIONS",
"works" : 2,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"children" : [
{
"type" : "FETCH",
"works" : 2,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"alreadyHasObj" : 0,
"forcedFetches" : 0,
"matchTested" : 1,
"children" : [
{
"type" : "IXSCAN",
"works" : 2,
"yields" : 0,
"unyields" : 0,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1,
"keyPattern" : "{ data.point: 1.0 }",
"isMultiKey" : 1,
"boundsVerbose" : "field #0['data.point']: [2.0, 2.0]",
"yieldMovedCursor" : 0,
"dupsTested" : 1,
"dupsDropped" : 0,
"seenInvalidated" : 0,
"matchTested" : 0,
"keysExamined" : 1,
"children" : [ ]
}
]
}
]
},
"millis" : 0
}
... もう一度言います - 何か見逃しましたか? 私はそれを間違っていますか?回避策はありますか?これはバグですか、それとも既知の問題ですか?
複製されたシャード クラスターで mongodb v2.6.5 を使用しています。