我想为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"}
}
}
}
])
看来,它正在工作,但是性能太慢。我怎样才能更快呢?
答案 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
是可选的(但有点方便...)