MongoDB时间序列:引入聚合框架

在之前的文章中,我谈到了批处理导入开箱即用的MongoDB性能 。 同时,MongoDB被授予年度DBMS奖 ,因此我决定对它的实际使用情况进行更全面的分析。

因为在实际环境中可以更好地理解理论,所以我将首先向您介绍我们的虚拟项目要求。

介绍


我们的虚拟项目具有以下要求:

  1. 它必须存储表示为v = f(t)的有价值的时间事件
  2. 它必须通过以下方式汇总最小,最大,平均和计数记录:
    • 一分钟内
    • 一小时内
    • 一天中的几个小时
    • 一年中的天数
  3. 一分钟汇总中的秒数是实时计算的(因此必须非常快)
  4. 所有其他聚合均由批处理程序计算(因此它们必须相对较快)

资料模型

我将提供两个数据建模变体,每个变体各有利弊。

  1. 第一个版本使用默认的自动分配的MongoDB “ _id” ,这简化了插入操作,因为我们可以分批完成它,而不必担心任何时间戳冲突。
    如果每毫秒记录10个值,那么我们最终将拥有10个不同的文档。 这篇文章将讨论这个数据模型选项。
    {
    	"_id" : ObjectId("52cb898bed4bd6c24ae06a9e"),
    	"created_on" : ISODate("2012-11-02T01:23:54.010Z")
    	"value" : 0.19186609564349055
    }
  2. 第二个版本使用自纪元以来的毫秒数作为“ _id”字段,并且值存储在“值”数组中。
    如果每毫秒记录10个值,那么我们最终将得到一个不同的文档,其中“ values”数组中包含10个条目。 以后的文章将专门介绍这种压缩数据模型。
    {
            "_id" : 1348436178673,
            "values" : [
                    0.7518879524432123,
                    0.0017396819312125444
            ]
    }

插入资料

就像我以前的文章一样,我将使用5000万个文档来测试聚合逻辑。 我选择此数字是因为我正在商用PC上进行测试。 在前面提到的帖子中,我设法每秒插入超过80000个文档。 这次,我将采用更实际的方法,并在插入数据之前先创建集合和索引。

MongoDB shell version: 2.4.6
connecting to: random
> db.dropDatabase()
{ "dropped" : "random", "ok" : 1 }
> db.createCollection("randomData");
{ "ok" : 1 }
> db.randomData.ensureIndex({"created_on" : 1});
> db.randomData.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "ns" : "random.randomData",
                "name" : "_id_"
        },
        {
                "v" : 1,
                "key" : {
                        "created_on" : 1
                },
                "ns" : "random.randomData",
                "name" : "created_on_1"
        }
]

现在是时候插入50M文档了。

mongo random --eval "var arg1=50000000;arg2=1" create_random.js
...
Job#1 inserted 49900000 documents.
Job#1 inserted 50000000 in 2852.56s

这次我们设法每秒导入17500个文档。 以这样的速度,我们每年需要550B条目,对于我们的用例而言,这绰绰有余。

压缩数据

首先,我们需要分析集合统计信息,为此,我们需要使用stats命令:

db.randomData.stats()
{
        "ns" : "random.randomData",
        "count" : 50000000,
        "size" : 3200000096,
        "avgObjSize" : 64.00000192,
        "storageSize" : 5297451008,
        "numExtents" : 23,
        "nindexes" : 2,
        "lastExtentSize" : 1378918400,
        "paddingFactor" : 1,
        "systemFlags" : 1,
        "userFlags" : 0,
        "totalIndexSize" : 3497651920,
        "indexSizes" : {
                "_id_" : 1623442912,
                "created_on_1" : 1874209008
        },
        "ok" : 1
}

当前索引大小几乎是3.5GB,几乎是我可用RAM的一半。 幸运的是,MongoDB附带了一个紧凑的命令,我们可以使用该命令对数据进行碎片整理。 这会花费很多时间,特别是因为我们的索引总大小很大。

db.randomData.runCommand("compact");
Compacting took 1523.085s

让我们看看通过压缩节省了多少空间:

db.randomData.stats()
{
        "ns" : "random.randomData",
        "count" : 50000000,
        "size" : 3200000032,
        "avgObjSize" : 64.00000064,
        "storageSize" : 4415811584,
        "numExtents" : 24,
        "nindexes" : 2,
        "lastExtentSize" : 1149206528,
        "paddingFactor" : 1,
        "systemFlags" : 1,
        "userFlags" : 0,
        "totalIndexSize" : 2717890448,
        "indexSizes" : {
                "_id_" : 1460021024,
                "created_on_1" : 1257869424
        },
        "ok" : 1
}

我们释放了将近800MB的数据,这对于我们的RAM密集型聚合操作非常方便。

解释聚合逻辑

所有四个汇总报告都是相似的,只是它们之间的区别在于:

  1. 选择时间间隔
  2. 按时间粒度分组

因此,我们可以从第一个报告开始,该报告按秒汇总值。 我们将使用explain方法来了解聚合的内部工作情况。

load(pwd() + "/../../util/date_util.js");
var minDate = new Date(Date.UTC(2012, 1, 10, 11, 25, 30));
var maxDate = new Date(Date.UTC(2012, 1, 10, 11, 25, 35));
var result = db.randomData.runCommand('aggregate', { pipeline: 
[
	{
		$match: {
			"created_on" : {
				$gte: minDate, 
				$lt : maxDate	
			}
		}
	},
	{
		$project: {
			_id : 0,
			created_on : 1,
			value : 1
		}
	},
	{
		$group: {
				"_id": { 
					"year" : {
						$year : "$created_on"
					}, 
					"dayOfYear" : {
						$dayOfYear : "$created_on"
					},
					"hour" : {
						$hour : "$created_on"
					},
					"minute" : {
						$minute : "$created_on"
					},
					"second" : {
						$second : "$created_on"
					},
				}, 
				"count": { 
					$sum: 1 
				}, 
				"avg": { 
					$avg: "$value" 
				}, 
				"min": { 
					$min: "$value" 
				}, 
				"max": { 
					$max: "$value" 
				}		
			}
	},
	{
		$sort: { 
			"_id.year" : 1, 
			"_id.dayOfYear" : 1,
			"_id.hour" : 1,
			"_id.minute" : 1,
			"_id.second" : 1
		} 	
	}
], explain: true});
printjson(result);

输出以下结果

{
        "serverPipeline" : [
                {
                        "query" : {
                                "created_on" : {
                                        "$gte" : ISODate("2012-02-10T11:25:30Z"),
                                        "$lt" : ISODate("2012-02-10T11:25:35Z")
                                }
                        },
                        "projection" : {
                                "created_on" : 1,
                                "value" : 1,
                                "_id" : 0
                        },
                        "cursor" : {
                                "cursor" : "BtreeCursor created_on_1",
                                "isMultiKey" : false,
                                "n" : 5,
                                "nscannedObjects" : 5,
                                "nscanned" : 5,
                                "nscannedObjectsAllPlans" : 5,
                                "nscannedAllPlans" : 5,
                                "scanAndOrder" : false,
                                "indexOnly" : false,
                                "nYields" : 0,
                                "nChunkSkips" : 0,
                                "millis" : 0,
                                "indexBounds" : {
                                        "created_on" : [
                                                [
                                                        ISODate("2012-02-10T11:25:30Z"),
                                                        ISODate("2012-02-10T11:25:35Z")
                                                ]
                                        ]
                                },
                                "allPlans" : [
                                        {
                                                "cursor" : "BtreeCursor created_on_1",
                                                "n" : 5,
                                                "nscannedObjects" : 5,
                                                "nscanned" : 5,
                                                "indexBounds" : {
                                                        "created_on" : [
                                                                [
                                                                        ISODate("2012-02-10T11:25:30Z"),
                                                                        ISODate("2012-02-10T11:25:35Z")
                                                                ]
                                                        ]
                                                }
                                        }
                                ],
                                "oldPlan" : {
                                        "cursor" : "BtreeCursor created_on_1",
                                        "indexBounds" : {
                                                "created_on" : [
                                                        [
                                                                ISODate("2012-02-10T11:25:30Z"),
                                                                ISODate("2012-02-10T11:25:35Z")
                                                        ]
                                                ]
                                        }
                                },
                                "server" : "VLAD:27017"
                        }
                },
                {
                        "$project" : {
                                "_id" : false,
                                "created_on" : true,
                                "value" : true
                        }
                },
                {
                        "$group" : {
                                "_id" : {
                                        "year" : {
                                                "$year" : [
                                                        "$created_on"
                                                ]
                                        },
                                        "dayOfYear" : {
                                                "$dayOfYear" : [
                                                        "$created_on"
                                                ]
                                        },
                                        "hour" : {
                                                "$hour" : [
                                                        "$created_on"
                                                ]
                                        },
                                        "minute" : {
                                                "$minute" : [
                                                        "$created_on"
                                                ]
                                        },
                                        "second" : {
                                                "$second" : [
                                                        "$created_on"
                                                ]
                                        }
                                },
                                "count" : {
                                        "$sum" : {
                                                "$const" : 1
                                        }
                                },
                                "avg" : {
                                        "$avg" : "$value"
                                },
                                "min" : {
                                        "$min" : "$value"
                                },
                                "max" : {
                                        "$max" : "$value"
                                }
                        }
                },
                {
                        "$sort" : {
                                "sortKey" : {
                                        "_id.year" : 1,
                                        "_id.dayOfYear" : 1,
                                        "_id.hour" : 1,
                                        "_id.minute" : 1,
                                        "_id.second" : 1
                                }
                        }
                }
        ],
        "ok" : 1
}

聚合框架使用管道和过滤器设计模式,我们的管道包括以下操作:

  1. 匹配 :此操作与WHERE SQL子句相似,这是自使用“ created_on”索引以来我们使用的第一个子句(例如,已通过解释结果确认: “ cursor”:“ BtreeCursor created_on_1” ,) 。 我们没有使用coverage -index(例如, “ indexOnly”:false ),因为这对于我们的8GB RAM设置来说是过分的。
  2. 项目 :此操作类似于SELECT SQL子句,用于从工作集中删除“ _id”字段(这对我们的报告逻辑没有用)。
  3. Group :此操作类似于GROUP BY SQL子句,并且它在内存中进行所有计算。 这就是为什么我们在分组之前过滤工作集的原因。
  4. Sort :此操作类似于ORDER BY SQL子句,我们使用它按时间顺序对结果进行排序。

基本聚合脚本

由于我们的四个报告相似,因此我们可以将所有逻辑分组在一个脚本中:

function printResult(dataSet) {
	dataSet.result.forEach(function(document)  {
		printjson(document);
	});
}

function aggregateData(fromDate, toDate, groupDeltaMillis, enablePrintResult) {		

	print("Aggregating from " + fromDate + " to " + toDate);

	var start = new Date();

	var groupBy = { 
		"year" : {
			$year : "$created_on"
		}, 
		"dayOfYear" : {
			$dayOfYear : "$created_on"
		}
	};

	var sortBy = { 
			"_id.year" : 1, 
			"_id.dayOfYear" : 1
	}; 	

	var appendSeconds = false;
	var appendMinutes = false;
	var appendHours = false;

	switch(groupDeltaMillis) {
		case ONE_SECOND_MILLIS :
			appendSeconds = true;			
		case ONE_MINUTE_MILLIS :
			appendMinutes = true;			
		case ONE_HOUR_MILLIS :
			appendHours = true;		
	}	

	if(appendHours) {
		groupBy["hour"] = {
			$hour : "$created_on"	
		};
		sortBy["_id.hour"] = 1;	
	}
	if(appendMinutes) {
		groupBy["minute"] = {
			$minute : "$created_on"	
		};
		sortBy["_id.minute"] = 1;
	}
	if(appendSeconds) {
		groupBy["second"] = {
			$second : "$created_on"	
		};
		sortBy["_id.second"] = 1;
	}	

	var pipeline = [
		{
			$match: {
				"created_on" : {
					$gte: fromDate, 
					$lt : toDate	
				}
			}
		},
		{
			$project: {
				_id : 0,
				created_on : 1,
				value : 1
			}
		},
		{
			$group: {
					"_id": groupBy, 
					"count": { 
						$sum: 1 
					}, 
					"avg": { 
						$avg: "$value" 
					}, 
					"min": { 
						$min: "$value" 
					}, 
					"max": { 
						$max: "$value" 
					}		
				}
		},
		{
			$sort: sortBy
		}
	];

	var dataSet = db.randomData.aggregate(pipeline);
	var aggregationDuration = (new Date().getTime() - start.getTime())/1000;	
	print("Aggregation took:" + aggregationDuration + "s");	
	if(dataSet.result != null && dataSet.result.length > 0) {
		print("Fetched :" + dataSet.result.length + " documents.");
		if(enablePrintResult) {
			printResult(dataSet);
		}
	}
	var aggregationAndFetchDuration = (new Date().getTime() - start.getTime())/1000;
	if(enablePrintResult) {
		print("Aggregation and fetch took:" + aggregationAndFetchDuration + "s");
	}	
	return {
		aggregationDuration : aggregationDuration,
		aggregationAndFetchDuration : aggregationAndFetchDuration
	};
}

取得成果的时间

让我们使用以下脚本测试前三个报告:

load(pwd() + "/../../util/date_util.js");
load(pwd() + "/aggregate_base_report.js");

var deltas = [ 
{
	matchDeltaMillis: ONE_MINUTE_MILLIS, 
	groupDeltaMillis: ONE_SECOND_MILLIS,
	description: "Aggregate all seconds in a minute"
},
{
	matchDeltaMillis: ONE_HOUR_MILLIS, 
	groupDeltaMillis: ONE_MINUTE_MILLIS,
	description: "Aggregate all minutes in an hour"
},
{
	matchDeltaMillis: ONE_DAY_MILLIS, 
	groupDeltaMillis: ONE_HOUR_MILLIS,
	description: "Aggregate all hours in a day"
}
];

var testFromDate = new Date(Date.UTC(2012, 5, 10, 11, 25, 59));

deltas.forEach(function(delta) {	
	print('Aggregating ' + description);
	var timeInterval = calibrateTimeInterval(testFromDate, delta.matchDeltaMillis);
	var fromDate = timeInterval.fromDate;
	var toDate = timeInterval.toDate;
	aggregateData(fromDate, toDate, delta.groupDeltaMillis, true);	
});

给我们以下结果:

MongoDB shell version: 2.4.6
connecting to: random
Aggregating Aggregate all seconds in a minute
Aggregating from Sun Jun 10 2012 14:25:00 GMT+0300 (GTB Daylight Time) to Sun Jun 10 2012 14:26:00 GMT+0300 (GTB Daylight Time)
Fetched :45 documents.
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 11,
                "minute" : 25,
                "second" : 0
        },
        "count" : 1,
        "avg" : 0.4924355132970959,
        "min" : 0.4924355132970959,
        "max" : 0.4924355132970959
}
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 11,
                "minute" : 25,
                "second" : 1
        },
        "count" : 1,
        "avg" : 0.10043778014369309,
        "min" : 0.10043778014369309,
        "max" : 0.10043778014369309
}
...
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 11,
                "minute" : 25,
                "second" : 59
        },
        "count" : 1,
        "avg" : 0.16304525500163436,
        "min" : 0.16304525500163436,
        "max" : 0.16304525500163436
}
Aggregating from Sun Jun 10 2012 14:00:00 GMT+0300 (GTB Daylight Time) to Sun Jun 10 2012 15:00:00 GMT+0300 (GTB Daylight Time)
Fetched :60 documents.
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 11,
                "minute" : 0
        },
        "count" : 98,
        "avg" : 0.4758610369979727,
        "min" : 0.004005654249340296,
        "max" : 0.9938081130385399
}
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 11,
                "minute" : 1
        },
        "count" : 100,
        "avg" : 0.5217278444720432,
        "min" : 0.003654648782685399,
        "max" : 0.9981840122491121
}
...
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 11,
                "minute" : 59
        },
        "count" : 92,
        "avg" : 0.5401836506308705,
        "min" : 0.01764239347539842,
        "max" : 0.9997266652062535
}
Aggregating Aggregate all hours in a day
Aggregating from Sun Jun 10 2012 03:00:00 GMT+0300 (GTB Daylight Time) to Mon Jun 11 2012 03:00:00 GMT+0300 (GTB Daylight Time)
Fetched :24 documents.
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 0
        },
        "count" : 5727,
        "avg" : 0.4975644027204364,
        "min" : 0.00020139524713158607,
        "max" : 0.9997993060387671
}
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 1
        },
        "count" : 5799,
        "avg" : 0.49519448930962623,
        "min" : 0.00011728447861969471,
        "max" : 0.9999530822969973
}
...
{
        "_id" : {
                "year" : 2012,
                "dayOfYear" : 162,
                "hour" : 23
        },
        "count" : 5598,
        "avg" : 0.49947314951339256,
        "min" : 0.00009276834316551685,
        "max" : 0.9999523421283811
}

请继续关注,我的下一篇文章将向您展示如何优化这些聚合查询。

参考: MongoDB时间序列:Vlad Mihalcea的Blog博客中,我们的JCG合作伙伴 Vlad Mihalcea 介绍了聚合框架

翻译自: https://www.javacodegeeks.com/2014/01/mongodb-time-series-introducing-the-aggregation-framework.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值