Расчет скользящей средней за каждые 5 секунд в MongoDB - PullRequest
0 голосов
/ 26 июня 2018

Я хочу рассчитать скользящее среднее для моих данных в 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 голосов
/ 03 сентября 2018

Я воспроизвел поведение, которое вы описываете, с 2-дневными наблюдениями за 1 секунду в БД и $match, который тянет всего за один день. Агг работает "отлично", если вы ведете, скажем, 60 секунд. Но 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 не является обязательным (но это удобно ...)

...