按时间间隔PHP进行MongoDB聚合

时间:2014-06-07 20:27:08

标签: php mongodb highcharts aggregation-framework

我使用MongoDB来存储每15秒捕获一次的服务器统计信息(因此每个服务器每分钟插入4行),并尝试将这些数据绘制到图表上,以显示某个时间戳之间的所有数据。

例如,可以使用以下查询:

$tbl->find(
  array(
    "timestamp" => array('$gte' => '1396310400', '$lte' => '1396915200'), 
    "service" => 'a715feac3db42f54edbc50ef6fa057b3'
  ),
  array("timestamp" => 1, "system" => 1)
);

我们的一堆行看起来像这样:

Array
(
    [53933ad8532965621d97dd3b] => Array
        (
            [_id] => MongoId Object
                (
                    [$id] => 53933ad8532965621d97dd3b
                )

            [system] => Array
                (
                    [load] => 0.55
                    [uptime] => 1171204.47
                    [processes] => 222
                )

            [timestamp] => 1396310403
        )

)

这适用于小数据范围,因为我可以将这些数据直接传递给Flot或HighCharts,并让它美化时间尺度本身。但是,这对大型数据集不起作用(例如查询一个月以上)。

我尝试做的是按小时(或15分钟)对数据进行分组,并返回给定时间内的平均值(在此示例中,为我绘制的system.load)周期。

我知道聚合功能是我需要使用的功能,但尽管我付出了最大的努力,但我还是无法实现这一功能。

现在我让PHP完成所有工作(按时间戳对结果进行分组并计算出平均值),但它非常慢,我知道MongoDB会更好地处理它。

非常感谢任何见解!

编辑: 我一直试图按照这里发布的答案,但仍在努力 - MongoDB Aggregation PHP, Group by Hours

1 个答案:

答案 0 :(得分:1)

我在您的问题顶部查看您的初始查询,并立即告诉我您的"时间戳"值实际上是字符串。毫无疑问,当您阅读这些信息并进行手动聚合时,#34;实际上,你正在将这些值,以及可能的其他值转换为可以操作,求和和平均的类型。

因此,这里的第一部分是修复您的数据,看起来它来自日志源,但您从未转换过这些值。我认为这不仅仅是时间戳值,而且可能也是系统下的指标。

这使您可以选择如何存储时间戳。您可以将其保留为当前字符串形式的时间戳编号,也可以选择转换为BSON date type。第一个将是一个简单的整数转换并保存回来,另一个你应该能够提供给驱动程序支持的Date类型,并再次保存数据。

完成此操作后,您可以愉快地使用聚合功能。因此,如果您选择将其保留为数字,那么您只需应用日期数学以获得分组边界:

db.collection.aggregate([

   // Match documents on the range you want
   { "$match": {
       "timestamp": {
           "$gte": 1396310400, "$lte": 1396915200
       },
       "service": "a715feac3db42f54edbc50ef6fa057b3"
   }},

   // Group on the time intervals, 15 minutes here
   { "$group": {
       "_id": { 
           "service": "$service",
           "time": {
               "$subtract": [
                   "$timestamp",
                   { "$mod": [ "$timestamp", 60 * 15 ] }
               ]
           }
       },
       "load": { "$avg": "$system.load" }
   }},

   // Project to the output form you want
   { "$project": {      
       "service": "$_id.service",
       "time" : "$_id.time",
       "load": 1
   }}
])

或者是特定于php的

$tbl->aggregate(array(
    array(      
        '$match' => array(
            'timestamp' => array(
                '$gte' => 1396310400, '$lte' => 1396915200
            ),
            'service' => 'a715feac3db42f54edbc50ef6fa057b3'
        )
    ),
    array(
        '$group' => array(
            '_id' => array(
                'service' => '$service',
                'time' => array(
                    '$subtract' => array(
                       '$timestamp',
                       array( '$mod' => array('$timestamp', 60 * 15 ) )
                    )
                )
            ),
            'load' => array( '$avg' => '$system.load' )
        )
    ),
    array(
        '$project' => array(
            'service' => '$_id.service',
            'time' => '$_id.time',
            'load' => 1
        )
    )
))

否则,如果您选择转换为BSON日期,则可以改为使用date aggregation operators

db.collection.aggregate([
   { "$match": {
       "timestamp": {
           "$gte": new Date("2014-04-01"), "$lte": new Date("2014-04-08")
       },
       "service": "a715feac3db42f54edbc50ef6fa057b3"
   }},
   { "$group": {
       "service": "$service",
       "time": {
           "dayOfYear": { "$dayOfYear": "$timestamp" },
           "hour": { "$hour": "$timestamp" },
           "minute": {
               "$subtract": [
                   { "$minute": "$timestamp" },
                   { 
                       "$mod": [
                           { "$minute": "$timestamp" },
                           15
                       ]
                   }
               ]
           }
       },
       "load": { "$avg": "$system.load" }
   }},
   { "$project": {
       "service": "$_id.service",
       "time": "$_id.time",
       "load": 1
   }}
])

所以你有date aggregation operators的帮助来分解你所拥有的部分日期,并且仍然使用相同的模运算来获得间隔值。

如果您仍然喜欢日期数学方法,您仍然可以使用日期对象执行此操作,因为从另一个日期对象中减去一个日期对象的结果将是纪元时间戳值。因此,将BSON日期移至纪元时间戳只需要:

{
    "$subtract": [
        "$dateObjectField",
        new Date("1970-01-01")
    ]
}

所以" date"您传入管道的值可以使用驱动程序的本机类型方法进行转换,并在将请求发送到MongoDB时正确序列化。另一个优点是当您阅读它们时也是如此,因此不再需要在客户端处理中进行转换。