sql >> データベース >  >> NoSQL >> MongoDB

Mongodbは、グループ内の並べ替えと制限を集約します

    基本的な問題

    近い将来、現在のところ、集約フレームワークでこれを実行しようとするのは、世の中で最も賢明な考えではありません。もちろん、主な問題は、すでに持っているコードの次の行にあります。

    "items" : { "$push": "$$ROOT" }
    

    つまり、基本的に発生する必要があるのは、グループ化キー内のすべてのオブジェクトを配列にプッシュして、後のコードで「上位N」の結果を取得する必要があるということです。

    最終的には、そのアレイ自体のサイズがBSONの制限である16MBを超える可能性があり、グループ化されたドキュメント内の残りのデータに関係なく、これは明らかにスケーリングしません。ここでの主な落とし穴は、「プッシュを特定の数のアイテムだけに制限する」ことはできないということです。そのようなことについては、長年のJIRAの問題があります。

    その理由だけで、これに対する最も実用的なアプローチは、各グループ化キーの「上位N」項目に対して個別のクエリを実行することです。これらは.aggregate()である必要はありません。 ステートメント(データによって異なります)であり、実際には、必要な「上位N」の値を単純に制限するものであれば何でもかまいません。

    ベストアプローチ

    あなたのアーキテクチャはnode.jsにあるようです mongooseで 、ただし、非同期IOとクエリの並列実行をサポートするものはすべて最良のオプションになります。理想的には、それらのクエリの結果を単一の応答に結合することをサポートする独自のAPIライブラリを備えたものです。

    たとえば、アーキテクチャと利用可能なライブラリ(特にasync)を使用したこの簡略化された例のリストがあります )これは、この並列および結合された結果を正確に実行します:

    var async = require('async'),
        mongoose = require('mongoose'),
        Schema = mongoose.Schema;
    
    mongoose.connect('mongodb://localhost/test');
    
    var data = [
      { "merchant": 1, "rating": 1 },
      { "merchant": 1, "rating": 2 },
      { "merchant": 1, "rating": 3 },
      { "merchant": 2, "rating": 1 },
      { "merchant": 2, "rating": 2 },
      { "merchant": 2, "rating": 3 }
    ];
    
    var testSchema = new Schema({
      merchant: Number,
      rating: Number
    });
    
    var Test = mongoose.model( 'Test', testSchema, 'test' );
    
    async.series(
      [
        function(callback) {
          Test.remove({},callback);
        },
        function(callback) {
          async.each(data,function(item,callback) {
            Test.create(item,callback);
          },callback);
        },
        function(callback) {
          async.waterfall(
            [
              function(callback) {
                Test.distinct("merchant",callback);
              },
              function(merchants,callback) {
                async.concat(
                  merchants,
                  function(merchant,callback) {
                    Test.find({ "merchant": merchant })
                      .sort({ "rating": -1 })
                      .limit(2)
                      .exec(callback);
                  },
                  function(err,results) {
                    console.log(JSON.stringify(results,undefined,2));
                    callback(err);
                  }
                );
              }
            ],
            callback
          );
        }
      ],
      function(err) {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    これにより、出力の各マーチャントの上位2つの結果のみが表示されます。

    [
      {
        "_id": "560d153669fab495071553ce",
        "merchant": 1,
        "rating": 3,
        "__v": 0
      },
      {
        "_id": "560d153669fab495071553cd",
        "merchant": 1,
        "rating": 2,
        "__v": 0
      },
      {
        "_id": "560d153669fab495071553d1",
        "merchant": 2,
        "rating": 3,
        "__v": 0
      },
      {
        "_id": "560d153669fab495071553d0",
        "merchant": 2,
        "rating": 2,
        "__v": 0
      }
    ]
    

    これは実際にはこれを処理するための最も効率的な方法ですが、それでも複数のクエリであるため、リソースが必要になります。しかし、すべてのドキュメントを配列に格納して処理しようとすると、集約パイプラインで消費されるリソースにはほど遠いです。

    現在および近い将来の総合的な問題

    その行に、ドキュメントの数がこれを行うことができるBSON制限の違反を引き起こさないと考えることは可能です。 MongoDBの現在のリリースのメソッドはこれには適していませんが、次のリリース(執筆時点では、3.1.8 devブランチがこれを行います)は少なくとも$sliceを導入します。 集約パイプラインへの演算子。したがって、集計操作に精通していて、 $sortを使用する場合 まず、配列内ですでに並べ替えられているアイテムを簡単に選択できます。

    var async = require('async'),
        mongoose = require('mongoose'),
        Schema = mongoose.Schema;
    
    mongoose.connect('mongodb://localhost/test');
    
    var data = [
      { "merchant": 1, "rating": 1 },
      { "merchant": 1, "rating": 2 },
      { "merchant": 1, "rating": 3 },
      { "merchant": 2, "rating": 1 },
      { "merchant": 2, "rating": 2 },
      { "merchant": 2, "rating": 3 }
    ];
    
    var testSchema = new Schema({
      merchant: Number,
      rating: Number
    });
    
    var Test = mongoose.model( 'Test', testSchema, 'test' );
    
    async.series(
      [
        function(callback) {
          Test.remove({},callback);
        },
        function(callback) {
          async.each(data,function(item,callback) {
            Test.create(item,callback);
          },callback);
        },
        function(callback) {
          Test.aggregate(
            [
              { "$sort": { "merchant": 1, "rating": -1 } },
              { "$group": {
                "_id": "$merchant",
                "items": { "$push": "$$ROOT" }
              }},
              { "$project": {
                "items": { "$slice": [ "$items", 2 ] }
              }}
            ],
            function(err,results) {
              console.log(JSON.stringify(results,undefined,2));
              callback(err);
            }
          );
        }
      ],
      function(err) {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    これは、最初に並べ替えられた後、上位2つのアイテムが配列から「スライス」されるのと同じ基本的な結果をもたらします。

    また、現在のリリースでは実際には「可能」ですが、同じ基本的な制約があり、最初にコンテンツを並べ替えた後でも、すべてのコンテンツを配列にプッシュする必要があります。それは単に「反復的な」アプローチを取ります。これをコード化して、より多くのエントリの集計パイプラインを生成できますが、「2」と表示するだけで、試してみるのはあまり良い考えではないことがわかります。

    var async = require('async'),
        mongoose = require('mongoose'),
        Schema = mongoose.Schema;
    
    mongoose.connect('mongodb://localhost/test');
    
    var data = [
      { "merchant": 1, "rating": 1 },
      { "merchant": 1, "rating": 2 },
      { "merchant": 1, "rating": 3 },
      { "merchant": 2, "rating": 1 },
      { "merchant": 2, "rating": 2 },
      { "merchant": 2, "rating": 3 }
    ];
    
    var testSchema = new Schema({
      merchant: Number,
      rating: Number
    });
    
    var Test = mongoose.model( 'Test', testSchema, 'test' );
    
    async.series(
      [
        function(callback) {
          Test.remove({},callback);
        },
        function(callback) {
          async.each(data,function(item,callback) {
            Test.create(item,callback);
          },callback);
        },
        function(callback) {
          Test.aggregate(
            [
              { "$sort": { "merchant": 1, "rating": -1 } },
              { "$group": {
                "_id": "$merchant",
                "items": { "$push": "$$ROOT" }
              }},
              { "$unwind": "$items" },
              { "$group": {
                "_id": "$_id",
                "first": { "$first": "$items" },
                "items": { "$push": "$items" }
              }},
              { "$unwind": "$items" },
              { "$redact": {
                "$cond": [
                  { "$eq": [ "$items", "$first" ] },
                  "$$PRUNE",
                  "$$KEEP"
                ]
              }},
              { "$group": {
                "_id": "$_id",
                "first": { "$first": "$first" },
                "second": { "$first": "$items" }
              }},
              { "$project": {
                "items": {
                  "$map": {
                    "input": ["A","B"],
                    "as": "el",
                    "in": {
                      "$cond": [
                        { "$eq": [ "$$el", "A" ] },
                        "$first",
                        "$second"
                      ]
                    }
                  }
                }
              }}
            ],
            function(err,results) {
              console.log(JSON.stringify(results,undefined,2));
              callback(err);
            }
          );
        }
      ],
      function(err) {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    また、以前のバージョンでは「可能」でしたが(これは、すでに$$ROOTにタグを付けているため、2.6で導入された機能を使用して短縮しています )、基本的な手順は、配列を保存してから、 $firstを使用して各アイテムを「スタックから外す」ことです。 そして、それ(および場合によっては他のアイテム)を配列内のアイテムと比較してそれらを削除し、「トップN」が最終的に完了するまで、そのスタックから「次の最初の」アイテムを取得します。

    結論

    $push内のアイテムを許可する操作が行われる日が来るまで アグリゲーションアキュムレータを特定のカウントに制限する場合、これはアグリゲートの実際の操作ではありません。

    これらの結果に含まれるデータが十分に小さい場合はそれを行うことができ、データベースサーバーが実際の利点を提供するのに十分な仕様である場合は、クライアント側の処理よりも効率的である可能性があります。しかし、合理的な使用法のほとんどの実際のアプリケーションでは、どちらも当てはまらない可能性があります。

    最善の策は、最初に示した「並列クエリ」オプションを使用することです。それは常にうまくスケーリングし、特定のグループが少なくとも必要な「上位N」アイテムの合計を返さず、それらを保持する方法を考え出すようなロジックを「コード化」する必要はありません(省略されたより長い例) )各クエリを実行し、結果を組み合わせるだけです。

    並列クエリを使用します。それはあなたが持っているコード化されたアプローチよりも良くなるでしょう、そしてそれは長い道のりで示された集約アプローチよりも優れているでしょう。少なくともより良い選択肢があるまで。



    1. Redis:挿入された要素が最初または最後にある場合、ZADDはO(logN)よりも優れていますか?

    2. RedisとMongoDB

    3. php-redis-シリアル化せずにPHPオブジェクトをRedisに保存する方法はありますか?

    4. 追加の代わりにmongo$pushを追加することはできますか?