Mongo $ min和$ max,或并行排序

时间:2015-08-07 09:33:24

标签: node.js mongodb mongodb-query

您好我想在我的数据库中获取字段的minmax值。

我找到了这个查询和排序结果的解决方案: get max value in mongoose 我可以做两次并将它与async.parallel结合起来,将其写成非阻塞。但我猜两个数据库查询可能不是最佳解决方案。

第二种解决方案是使用聚合。但我不想把任何事情分组。我只想使用$match进行过滤(过滤条件总是差异,可以是{})并使用我的集合中的所有文档运行查询。

http://docs.mongodb.org/manual/reference/operator/aggregation/min/ http://docs.mongodb.org/manual/reference/operator/aggregation/max/

问题)

  1. 我可以在一个带有聚合的查询中运行它,也许可以使用$project
  2. 是否有另一种方法而不是aggregate 分组
  3. 1)/ 2)比排序的第一个解决方案更有时间效率吗?
  4. 编辑: 解决了第一个解决方案,但我认为有一个更有效的解决方案,因为这需要两个数据库操作:

        async.parallel
          min: (next) ->
            ImplantModel.findOne(newFilter).sort("serialNr").exec((err, result) ->
              return next err if err?
              return next null, 0 if !result?
              next(null, result.serialNr)
            )
          max: (next) ->
            ImplantModel.findOne(newFilter).sort("-serialNr").exec((err, result) ->
              return next err if err?
              return next null, 0 if !result?
              next(null, result.serialNr)
            )
          (err, results) ->
            console.log results.min, ' ', results.max
            return callback(err) if err?
            return callback null, {min:results.min, max:results.max}
      )
    

1 个答案:

答案 0 :(得分:2)

不知道这个问题究竟是什么,并且肯定不会从响应中得到真正的爱,但我不能让它继续睡觉而不解决。

所以首先要说的是我认为我欠这个10美元的OP,因为我的预期结果并非如此。

这里介绍的基本思想是对比:

  • 使用并行执行查询来查找字段中的“最大值”(已排序的总值)以及通过相同约束查找最小值

  • 汇总框架$max$min将累加器分组到整个集合中。

在“理论”中,这两个选项完全相同。并且在“理论”中,即使并行执行可以“同时”发生并且同时向服务器发出请求,在这些请求中仍然应该存在“开销”继承,并且客户端中的“聚合”功能将两个结果结合在一起。

此处的测试运行“系列”执行,创建合理密钥长度的随机数据,比较“公平”,此处的“密钥”数据也被编入索引。

下一个“公平”阶段是通过对所有项目执行顺序“提取”来“预热”数据,以模拟将尽可能多的“工作集”数据加载到内存中,因为客户端计算机能够

然后我们运行每个测试,比较和系列,以便不与每个资源竞争资源,对于“并行查询”情况或“聚合”情况,以查看定时器附加到开始和结束的结果每次执行。

这是我的测试平台脚本,关于尽可能保持精简的基本驱动程序(考虑nodejs环境):

var async = require('async'),
    mongodb = require('mongodb'),
    MongoClient = mongodb.MongoClient;

var total = 1000000;

MongoClient.connect('mongodb://localhost/bigjunk',function(err,db) {
  if (err) throw err;

  var a = 10000000000000000000000;

  db.collection('bigjunk',function(err,coll) {
    if (err) throw err;

    async.series(
      [
        // Clean data
        function(callback) {
          console.log("removing");
          coll.remove({},callback);
        },

        // Insert data
        function(callback) {
          var count = 0,
              bulk = coll.initializeUnorderedBulkOp();

          async.whilst(
            function() { return count < total },
            function(callback) {
              var randVal = Math.floor(Math.random(a)*a).toString(16);
              //console.log(randVal);
              bulk.insert({ "rand": randVal });
              count++;

              if ( count % 1000 == 0 ) {
                if ( count % 10000 == 0 ) {
                  console.log("counter: %s",count);     // log 10000
                }

                bulk.execute(function(err,res) {
                  bulk = coll.initializeUnorderedBulkOp();
                  callback();
                });
              } else {
                callback();
              }

            },
            callback
          );
        },

        // index the collection
        function(callback) {
          console.log("indexing");
          coll.createIndex({ "rand": 1 },callback);
        },

        // Warm up
        function(callback) {
          console.log("warming");
          var cursor = coll.find();

          cursor.on("error",function(err) {
            callback(err);
          });

          cursor.on("data",function(data) {
            // nuthin
          });

          cursor.on("end",function() {
            callback();
          });
        },

        /*
         * *** The tests **
         */

        // Parallel test
        function(callback) {
          console.log("parallel");
          console.log(Date.now());
          async.map(
            [1,-1],
            function(order,callback) {
                coll.findOne({},{ "sort": { "rand": order } },callback);
            },
            function(err,result) {
              console.log(Date.now());
              if (err) callback(err);
              console.log(result);
              callback();
            }
          );
        },

        function(callback) {
          console.log(Date.now());
          coll.aggregate(
            { "$group": {
              "_id": null,
              "min": { "$min": "$rand" },
              "max": { "$max": "$rand" }
            }},
            function(err,result) {
              console.log(Date.now());
              if (err) callback(err);
              console.log(result);
              callback();
            }
          );
        }
      ],
      function(err) {
        if (err) throw err;
        db.close();
      }
    );
  });
});

结果(与我的预期相比)是在“总体情况”中作出的。

10,000份文件

1438964189731
1438964189737
[ { _id: 55c4d9dc57c520412399bde4, rand: '1000bf6bda089c00000' },
  { _id: 55c4d9dd57c520412399c731, rand: 'fff95e4662e6600000' } ]
1438964189741
1438964189773
[ { _id: null,
    min: '1000bf6bda089c00000',
    max: 'fff95e4662e6600000' } ]

这表示并行情况的 6 ms 32ms 巨大差异。

这可以变得更好吗?

100,000 文件:

1438965011402
1438965011407
[ { _id: 55c4dd036902125223a05958, rand: '10003bab87750d00000' },
  { _id: 55c4dd066902125223a0a84a, rand: 'fffe9714df72980000' } ]
1438965011411
1438965011640
[ { _id: null,
    min: '10003bab87750d00000',
    max: 'fffe9714df72980000' } ]

结果仍然清晰显示 5 ms ,这接近于数据减少10倍的结果,并且在聚合情况下, 229 ms 更慢,几乎是因子10(增加的量)比前一个样本慢。

但等等,因为它变得更糟。让我们将样本增加到1,000,000个条目:

1,000,000 文件样本:

1438965648937
1438965648942
[ { _id: 55c4df7729cce9612303e39c, rand: '1000038ace6af800000' },
  { _id: 55c4df1029cce96123fa2195, rand: 'fffff7b34aa7300000' } ]
1438965648946
1438965651306
[ { _id: null,
    min: '1000038ace6af800000',
    max: 'fffff7b34aa7300000' } ]

这实际上是最糟糕的,因为“并行”案例仍然表现出 5ms 响应时间,“聚合”案例现在已经爆发为高峰 2360ms (哇,超过2整秒)。这只是被认为是完全不可接受的,因为它与替代接近时间的差别。这是执行周期的500倍,并且在计算方面是巨大

结论

除非你知道肯定的赢家,否则永远不要打赌。

聚合“应该”在这里获胜,因为结果背后的原理基本上与基本算法中的“并行执行情况”相同,以从可用的索引的键中选择结果。

这是一个“失败”(正如我的孩子们喜欢说的)聚合管道需要由某人(我的“半伙伴”擅长这些东西)来回到“算法学校”并且重新学习它表现较差的基础知识,以产生更快的结果。

所以这里的基本教训是:

  • 我们认为应该优化“聚合”累加器来做到这一点,但目前它们显然不是。

  • 您希望最快方式确定数据集合上的 min / max (没有和不同的键),然后使用{执行并行查询{1}} modfier实际上比任何替代方案都快得多。 (带索引)。

因此,对于想要对数据集合执行此操作的人,请使用此处所示的并行查询。它要快得多(直到我们可以教操作员更好:&gt;)

我应该注意,所有时序都与硬件相关,主要是时间的“比较”,这里有效。

这些结果来自我的(古代)笔记本电脑

  • Core I7 CPU(8x内核)
  • Windows 7主机(是的,无法重新安装)
  • 8GB RAM主机
  • 4GB分配的VM(4x核心分配)
  • VirtualBox 4.3.28
  • Ubuntu 15.04
  • MongoDB 3.1.6(开发)

此处列表中所需的最新“稳定”节点版本。