MongoDB:インデックス付けするにはフィールドが多すぎますか? ジェネリックインデックスを使用する

問題の本質



文書にさまざまなフィールドがあり、それらに効果的なクエリを実行する必要がある場合があります。 たとえば、人を説明するドキュメントがあります。



{ _id: 123, firstName: "John", lastName: "Smith", age: 25, height: 6.0, dob: Date, eyes: "blue", sign: "Capricorn", ... }
      
      







このような文書によると、目の色、特定の身長、姓、その他の特性に応じて人々を選択できます。 しかし、たとえば、ドキュメントが多数のフィールドで構成されている場合、または事前に知られていない場合、または各ドキュメントに独自のフィールドセットがある場合はどうでしょうか。 インデックスの助けを借りてこの問題を迅速に解決すると同時に、各フィールドごとにインデックスを作成しない方法。これはソリューションが高すぎるためです。



解決策1:フィールド名と値による複合インデックス



オブジェクトのリストの形式でドキュメントフィールドを保存する機能を使用して、ドキュメントスキームを設計しましょう。



 { _id: 123, props: [ { n: "firstName", v: "John"}, { n: "lastName", v: "Smith"}, { n: "age", v: 25}, ... ] }
      
      







この問題を解決するために、リスト内のオブジェクトの名前と値によって複合インデックスが作成されます。 明確にするために、 prop0



からprop0



までの架空のプロパティでprop0



のランダムな値を持つ500万のドキュメントを作成します。



 > for (var i = 0; i < 5000000; ++i) { var arr = []; for (var j = 0; j < 10; ++j) { arr.push({n: "prop" + j, v: Math.floor(Math.random() * 1000) }) }; db.generic.insert({props: arr}) } > db.generic.findOne() { "_id": ObjectId("515dd3b4f0bd676b816aa9b0"), "props": [ { "n": "prop0", "v": 40 }, { "n": "prop1", "v": 198 }, ... { "n": "prop9", "v": 652 } ] } > db.generic.ensureIndex({"props.n": 1, "props.v": 1}) > db.generic.stats() { "ns": "test.generic", "count": 5020473, "size": 1847534064, "avgObjSize": 368, "storageSize": 2600636416, "numExtents": 19, "nindexes": 2, "lastExtentSize": 680280064, "paddingFactor": 1, "systemFlags": 1, "userFlags": 0, "totalIndexSize": 1785352240, "indexSizes": { "_id_": 162898624, "props.n_1_props.v_1": 1622453616 }, "ok": 1 }
      
      







この場合、インデックスにはプロパティの名前と値の両方が格納されるため、インデックスサイズは1.6 GBです。 次に、 prop1



0



であるドキュメントを検索してみましょう。



 > db.generic.findOne({"props.n": "prop1", "props.v": 0}) { "_id": ObjectId("515dd4298bff7c34610f6ae8"), "props": [ { "n": "prop0", "v": 788 }, { "n": "prop1", "v": 0 }, ... { "n": "prop9", "v": 788 } ] } > db.generic.find({"props.n": "prop1", "props.v": 0}).explain() { "cursor": "BtreeCursor props.n_1_props.v_1", "isMultiKey": true, "n": 49822, "nscannedObjects": 5020473, "nscanned": 5020473, "nscannedObjectsAllPlans": 5020473, "nscannedAllPlans": 5020473, "scanAndOrder": false, "indexOnly": false, "nYields": 0, "nChunkSkips": 0, "millis": 252028, "indexBounds": { "props.n": [ [ "prop1", "prop1" ] ], "props.v": [ [ { "$minElement": 1 }, { "$maxElement": 1 } ] ] }, "server": "agmac.local:27017" }
      
      







このようなソリューションでは、期待した結果が得られませんでした。252秒で〜50,000件のドキュメントが見つかりました。 これは、各リクエストn=prop1



およびv=0



では、添付ドキュメントに対して両方の条件を同時に満たす必要がないため、要件n=prop1



およびv=0



両方を満たすドキュメントは最終結果にn=prop1



ため、これはまったく何でもないためです。期待した。 $elemMatch



を使用してリクエストを絞り込むことができます:



 > db.generic.findOne({"props": { $elemMatch: {n: "prop1", v: 0} }})
      
      







次に、MongoDB v2.2でインデックスがどのように使用され、クエリが実行されている時間を確認しましょう。



 > db.generic.find({"props": { $elemMatch: {n: "prop1", v: 0} }}).explain() { "cursor": "BtreeCursor props.n_1_props.v_1", "isMultiKey": true, "n": 5024, "nscannedObjects": 5020473, "nscanned": 5020473, "nscannedObjectsAllPlans": 5020473, "nscannedAllPlans": 5020473, "scanAndOrder": false, "indexOnly": false, "nYields": 0, "nChunkSkips": 0, "millis": 278784, "indexBounds": { "props.n": [ [ "prop1", "prop1" ] ], "props.v": [ [ { "$minElement": 1 }, { "$maxElement": 1 } ] ] }, "server": "agmac.local:27017" }
      
      







リクエストは正しく実行され、5024個のドキュメントが返されましたが、それでもまだ遅いです! explain



コマンドから、理由は範囲がまだフィールドv



使用されていることであることがわかります。 これが起こる理由を理解するために、例をより詳細に分析します。 $elemMatch



使用しない場合、クエリ条件の少なくとも1つを個別に満たすフィールドのすべての組み合わせが最終選択に含まれます。 この場合、インデックスを維持するために使用することは不可能です。なぜなら、可能な組み合わせが膨大になるからです。 そのため、MongoDBは、プロンプトが表示されたときに、添付ドキュメントの値からBツリーを構築し、可能な組み合わせ( $elemMatch



主な動作)を無視することを選択しました。 しかし、 $elemMatch



を使用したリクエストの実行速度が非常に遅いのはなぜですか? これは、MongoDB v2.4のSERVER-3104で修正されたバグが原因でした。 修正バージョンで同じリクエストを確認します。



 > db.generic.find({"props": { $elemMatch: {n: "prop1", v: 0} }}).explain() { "cursor": "BtreeCursor props.n_1_props.v_1", "isMultiKey": true, "n": 5024, "nscannedObjects": 5024, "nscanned": 5024, "nscannedObjectsAllPlans": 5024, "nscannedAllPlans": 5024, "scanAndOrder": false, "indexOnly": false, "nYields": 0, "nChunkSkips": 0, "millis": 21, "indexBounds": { "props.n": [ [ "prop1", "prop1" ] ], "props.v": [ [ 0, 0 ] ] }, "server": "agmac.local:27017" }
      
      







リクエストは21ミリ秒で完了しました!



ソリューション#2:1つの一般的なインデックス



この問題を解決する別の方法は、リストにフィールドをproperty: value



として保存することですproperty: value



オブジェクト。 このソリューションは、MongoDB v2.2およびv2.4で機能します。 フォームのドキュメントを作成します。



 > for (var i = 0; i < 5000000; ++i) { var arr = []; for (var j = 0; j < 10; ++j) { var doc = {}; doc["prop" + j] = Math.floor(Math.random() * 1000); arr.push(doc) }) }; db.generic2.insert({props: arr}) } > db.generic2.findOne() { "_id": ObjectId("515e5e6a71b0722678929760"), "props": [ { "prop0": 881 }, { "prop1": 47 }, ... { "prop9": 717 } ] }
      
      







インデックスを作成します。



 > db.generic2.ensureIndex({props: 1}) > db.generic2.stats() { "ns": "test.generic2", "count": 5000000, "size": 1360000032, "avgObjSize": 272.0000064, "storageSize": 1499676672, "numExtents": 19, "nindexes": 2, "lastExtentSize": 393670656, "paddingFactor": 1, "systemFlags": 1, "userFlags": 0, "totalIndexSize": 2384023488, "indexSizes": { "_id_": 162269072, "props_1": 2221754416 }, "ok": 1 }
      
      







インデックスサイズのサイズは約2.2 GBでした。これは、添付ドキュメントのBSON自体がBLOBとしてインデックスに保存されるため、ソリューション#1よりも40%大きくなりました。 クエリを実行します:



 > db.generic2.find({"props": {"prop1": 0} }).explain() { "cursor": "BtreeCursor props_1", "isMultiKey": true, "n": 4958, "nscannedObjects": 4958, "nscanned": 4958, "nscannedObjectsAllPlans": 4958, "nscannedAllPlans": 4958, "scanAndOrder": false, "indexOnly": false, "nYields": 0, "nChunkSkips": 0, "millis": 15, "indexBounds": { "props": [ [ { "prop1": 0 }, { "prop1": 0 } ] ] }, "server": "agmac.local:27017" }
      
      







リクエストは15ミリ秒で完了しました。これは最初のソリューションよりも高速です。 ただし、1つの条件があります。リクエストをコンパイルするときは、サブドキュメントオブジェクト全体を記述する必要があります。 prop1



prop1



場合、要求を満たすドキュメントを選択するには、要求を完了する必要があります。



 > db.generic2.find({"props": { $gte: {"prop1": 0}, $lte: {"prop1": 9} })
      
      







少し不便です。また、添付ドキュメントに他のフィールドがある場合は、リクエストの準備に参加する必要があります(添付ドキュメントはBLOBの形式で保存されるため)。

もう1つの制限もあります。フィールド値のみを個別にインデックス化することはできませんが、ソリューション1では、値10



すべてのドキュメントを検索するためにprops.v



インデックスを作成できます。 解決策2はこれを許可しません。



おわりに



MongoDB v2.4は、「ビッグデータ」プロジェクトに使用できる、多数のフィールドを持つドキュメントの共通インデックスを構築するためのシンプルで効果的なソリューションを提供することがわかります。



All Articles