【问题标题】:Calculating moving average for every 5 seconds in MongoDB在MongoDB中计算每5秒的移动平均值
【发布时间】:2018-06-26 14:34:47
【问题描述】:

我想计算 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"}
      }
    }
}
])

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

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    我在数据库中用 2 天 1 秒的观察值重现了您描述的行为,$match 仅提取了一天的值。如果您按 60 秒的时间存储,则 agg 可以“正常”工作。但是 15 秒的时间是 30 秒的 6 倍。每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 将负责捆绑插槽号。 $group 之后的 $sort 是可选的(但有点方便......)

    【讨论】:

      猜你喜欢
      • 2021-12-16
      • 2015-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      • 1970-01-01
      • 2015-05-03
      相关资源
      最近更新 更多