【问题标题】:MongoDB aggregation by time interval PHPMongoDB按时间间隔聚合PHP
【发布时间】:2014-06-07 20:27:08
【问题描述】:

我正在使用 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

【问题讨论】:

    标签: php mongodb highcharts aggregation-framework


    【解决方案1】:

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

    所以这里的第一部分是修复您的数据,看起来它来自日志记录源,但您从未转换过这些值。我正在考虑合理的可能,这不仅是时间戳值,还可能是系统下的指标。

    这让您可以选择如何存储您的时间戳。您可以将其保留为当前为字符串形式的时间戳编号,也可以选择转换为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")
        ]
    }
    

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

    【讨论】:

    • 嗨,尼尔,感谢您的全面回答。我将在接下来的 20 分钟左右实现这一点,并会报告。我希望将时间戳字段更改为 BSON 本机格式,但我还没有找到最好的方法。
    • 您的回答完美无缺,我只需要更正array( '$mod': array('$timestamp', 60 * 15 ) ) 中的: 现在,数据存储在 MySQL 中,因此对于我的测试数据,我编写了一个脚本来选择一次从 MySQL 批量数据并将行插入 MongoDB。它可能超出了这个问题的范围,但是我需要做些什么来将我当前的时间戳格式(unix 时间戳)转换为 Mongo 的可用格式?
    • @LeeBrooks 有 MongoDB 驱动程序的文档,包括 MongoDate
    • 是的,我刚刚阅读了这篇文章,一切顺利;我非常感谢你:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-15
    • 1970-01-01
    • 1970-01-01
    • 2018-02-28
    • 1970-01-01
    • 2012-04-14
    • 1970-01-01
    相关资源
    最近更新 更多