如何使用 mongodb 驱动程序以时间间隔聚合文档

How to aggregate documents with mongodb driver for nodejs with time interval

我目前卡在了这个问题上。 我 collection 喜欢:

{ 
    "_id": ObjectId("55820292e3dc84aa0c9c63bc"), 
    "creation_at": ISODate("2015-06-17T23:28:18.896Z"), 
    "cpu": 36, 
    "mem": "1.08" 
}

并且每 30 秒将一些日志放入其中。 我想按时间段获取摘要信息,例如:

[
 {
   'interval': ['2015-06-07 13:00:00', '2015-06-07 13:59:59'],
   'cpu': {
      'max': 20,
      'min': 1,
      'avg': 3
   },
   'memory': {
      'max': 40,
      'min': 35,
      'avg': 38
   }
 },

 {
   'interval': ['2015-06-07 14:00:00', '2015-06-07 14:59:59'],
   'cpu': { ... },
   'memory': { ... }
 },

  ...
  ...
  ...
]

从 mongo 文档中我发现这可以通过聚合框架来实现,但我坚持使用时间间隔的生成。 你能给我一些建议吗?

你的数据中的内存似乎是 "string",所以除非你转换它,否则这将是一个问题。使用数值,这对于 Date Aggregation Operators:

的聚合框架来说很容易
db.collection.aggregate([
    { "$group": {
        "_id": {
            "year": { "$year": "$creation_at" },
            "month": { "$month": "$creation_at" },
            "day": { "$day": "$creation_at" },
            "hour": { "$hour": "$creation_at" }
        },
        "minCpu": { "$min": "$cpu" },
        "maxCpu": { "$max": "$cpu" },
        "avgCpu": { "$avg": "$cpu" },
        "minMem": { "$min": "$mem" },
        "maxMem": { "$max": "$mem" },
        "avgMem": { "$avg": "$mem" }
    }}
])

这是一小时的间隔,但当然还有更细粒度的运算符。当然,如果没有进一步投影,聚合字段不能像您拥有的那样显示为 "nested"。但如果你愿意,你也可以这样做。

如果日期聚合运算符看起来过于简洁或 "timestamp" 值更适合您,那么您可以改用日期数学:

db.collection.aggregate([
    { "$group": {
        "_id": {
            "$subtract": [
                { "$subtract": [ "$creation_at", new Date("1970-01-01") ] },
                { "$mod": [
                    { "$subtract": [ "$creation_at", new Date("1970-01-01") ] },
                    1000 * 60 * 60
                ]}
            ]
        },
        "minCpu": { "$min": "$cpu" },
        "maxCpu": { "$max": "$cpu" },
        "avgCpu": { "$avg": "$cpu" },
        "minMem": { "$min": "$mem" },
        "maxMem": { "$max": "$mem" },
        "avgMem": { "$avg": "$mem" }
    }}
])

因为 "subtracting" 来自日期 returns 的 "epoch date" 值当前时间以毫秒为数字。

如果你的 "string" 是一个问题,你可以随时回退到 mapReduce:

db.collection.mapReduce(
    function() {
        var date = this.creation_at.valueOf()
            - ( this.creation_at.valueOf % ( 1000 * 60 * 60 ) );

        emit( new Date(date), {
            cpu: {
                min: parseInt(this.cpu),
                max: parseInt(this.cpu),
                avg: parseInt(this.cpu)
            },
            mem: {
                min: parseFloat(this.mem),
                max: parseFloat(this.mem),
                avg: parseFloat(this.mem)
            }
        })
    },
    function(key,values) {
        var result = {
            cpu: { min: 0, max: 0, avg: 0 },
            mem: { min: 0, max: 0, avg: 0 }
        };

        result.cpu.min = Math.min.apply( null, values.map(function(el) { 
             return el.cpu.min;
        }));
        result.cpu.max = Math.max.apply( null, values.map(function(el) { 
             return el.cpu.max;
        }));
        result.cpu.avg = Array.sum(values.map(function(el) { 
             return el.cpu.avg
        })) / values.length;

        result.mem.min = Math.min.apply( null, values.map(function(el) { 
             return el.mem.min;
        }));
        result.mem.max = Math.max.apply( null, values.map(function(el) { 
             return el.mem.max;
        }));
        result.mem.avg = Array.sum(values.map(function(el) { 
             return el.mem.avg
        })) / values.length;

        return result;
    },
    { "out": { "inline": 1 } }
)

并且可能比那种快速的 hacky 方法更好地清理 reducer 逻辑。

但基本原理是从日期中提取间隔以进行分组,然后对结果字段进行数学计算。