Я воспроизвел поведение, которое вы описываете, с 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
не является обязательным (но это удобно ...)