在MongoDB中每5秒计算一次移动平均值

时间:2018-06-26 14:34:48

标签: mongodb mongodb-query aggregation-framework

我想为MongoDB中的数据计算移动平均值。我的数据结构如下

{
    "_id" : NUUID("54ab1171-9c72-57bc-ba20-0a06b4f858b3"),    
    "DateTime" : ISODate("2018-05-30T21:31:05.957Z"),
    "Type" : 3,
    "Value" : NumberDecimal("15.905414991993847")
}

我想计算2天之内和每5秒内每种类型的平均值。在这种情况下,我将Type放入$match管道中,但我更喜欢将结果按Type分组,然后将结果按Type分开。我所做的是如下

var start = new Date("2018-05-30T21:31:05.957Z");
var end = new Date("2018-06-01T21:31:05.957Z");
var arr = new Array();
for (var i = 0; i < 34560; i++) {               
   start.setSeconds(start.getSeconds() + 5);
   if (start <= end)
   { 
    var a = new Date(start);
    arr.push(a);   
   }
}

db.Data.aggregate([
{$match:{"DateTime":{$gte:new Date("2018-05-30T21:31:05.957Z"), 
          $lte:new Date("2018-06-01T21:31:05.957Z")}, "Type":3}},
{$bucket: {
      groupBy: "$DateTime",
      boundaries: arr,
      default: "Other",
      output: {
        "count": { $sum: 1 },
        "Value": {$avg:"$Value"}
      }
    }
}
])

看来,它正在工作,但是性能太慢。我怎样才能更快呢?

1 个答案:

答案 0 :(得分:0)

我在数据库中用2天的1秒观察值和$match重现了一天的值来再现您描述的行为。如果您等待60秒,则agg会“正常”运行。但是15秒花费了6倍的时间,达到了30秒。每隔5秒? 144秒5秒产生了17280个水桶阵列。是的

所以我去了客户端,将所有43200个文档拖到客户端,并在javascript中创建了一个简单的线性搜索存储段槽查找器和计算。

c=db.foo.aggregate([
{$match:{"date":{$gte:new Date(osv), $lte:new Date(endv) }}}
                ]);

c.forEach(function(r) {
    var x = findSlot(arr, r['date']);

    if(buckets[x] == undefined) {
        buckets[x] = {lb: arr[x], ub: arr[x+1], n: 0, v:0};
    }
    var zz = buckets[x];
    zz['n']++;
    zz['v'] += r['val'];
});

这实际上工作得更快一些,但性能却差不多,大约是92秒。

接下来,我将findSlot中的线性搜索更改为二等分搜索。 5秒的存储桶从144秒缩短至 .750秒:快了近200倍。。这包括拖动43200条记录并运行上面的forEach和存储桶逻辑。因此,有理由认为$bucket可能不会使用很好的算法,而当存储桶数组的长度超过几百个时会遭受损失。

认识到这一点,我们可以改为使用$floor的开始时间和观察时间之间的增量来存储数据:

db.foo.aggregate([
    {$match:{"date":{$gte:now, $lte:new Date(endv) }}}

    // Bucket by turning offset from "now" into floor divided by the number           
    // of seconds of grouping.  In this way, the resulting number becomes the         
    // slot into the virtual buckets, e.g.:                                           
    // date            now            diff/1000   floor @ 5 seconds:                  
    // 1514764800000   1514764800000  0           0                                   
    // 1514764802000   1514764800000  2           0                                   
    // 1514764804000   1514764800000  4           0                                   
    // 1514764806000   1514764800000  6           1                                   
    // 1514764808000   1514764800000  8           1                                   
    // 1514764810000   1514764800000  10          2                                   
    ,{$addFields: {"ff": {$floor: {$divide: [ {$divide: [ {$subtract: [ "$date", now ]}, 1000.0 ]}, secondsBucket ] }} }}

    // Now just group by the numeric slot number!
    ,{$group: {_id: "$ff", n: {$sum:1}, tot: {$sum: "$val"}, avg: {$avg: "$val"}} }

    // Get it in 0-n order....                                                        
    ,{$sort: {_id: 1}}
                ]);

    found 17280 in 204 millis

因此,我们现在有一个服务器端解决方案,只需0.204秒,即快700倍。而且您不必对输入进行排序,因为$group将负责捆绑插槽号。在$sort之后的$group是可选的(但有点方便...)