mongodb指南






介绍

这是我们的MongoDB时间序列教程的第二部分,本文将专门针对性能调整。 在上一篇文章中 ,我向您介绍了我们的虚拟项目需求。

简而言之,从2012年1月1日到2013年1月1日,我们有5000万个时间事件,其结构如下:

{
    "_id" : ObjectId("52cb898bed4bd6c24ae06a9e"),
    "created_on" : ISODate("2012-11-02T01:23:54.010Z")
    "value" : 0.19186609564349055
}

我们希望汇总以下离散时间样本的最小值,最大值和平均值以及条目计数:

  1. 一分钟内的所有秒数
  2. 一小时内的所有分钟
  3. 一天中的所有时间

这是我们的基本测试脚本的样子:

var testFromDates = [
	new Date(Date.UTC(2012, 5, 10, 11, 25, 59)),
	new Date(Date.UTC(2012, 7, 23, 2, 15, 07)),
	new Date(Date.UTC(2012, 9, 25, 7, 18, 46)),
	new Date(Date.UTC(2012, 1, 27, 18, 45, 23)),
	new Date(Date.UTC(2012, 11, 12, 14, 59, 13))
];

function testFromDatesAggregation(matchDeltaMillis, groupDeltaMillis, type, enablePrintResult) {
	var aggregationTotalDuration = 0;
	var aggregationAndFetchTotalDuration = 0;
	testFromDates.forEach(function(testFromDate) {	
		var timeInterval = calibrateTimeInterval(testFromDate, matchDeltaMillis);
		var fromDate = timeInterval.fromDate;
		var toDate = timeInterval.toDate;
		var duration = aggregateData(fromDate, toDate, groupDeltaMillis, enablePrintResult);
		aggregationTotalDuration += duration.aggregationDuration;
		aggregationAndFetchTotalDuration += duration.aggregationAndFetchDuration;		
	});
	print(type + " aggregation took:" + aggregationTotalDuration/testFromDates.length + "s");
	if(enablePrintResult) {
		print(type + " aggregation and fetch took:" + aggregationAndFetchTotalDuration/testFromDates.length + "s");
	}
}

这就是我们将测试三个用例的方式:

testFromDatesAggregation(ONE_MINUTE_MILLIS, ONE_SECOND_MILLIS, 'One minute seconds');
testFromDatesAggregation(ONE_HOUR_MILLIS, ONE_MINUTE_MILLIS, 'One hour minutes');
testFromDatesAggregation(ONE_DAY_MILLIS, ONE_HOUR_MILLIS, 'One year days');

我们使用五个开始时间戳,这些时间戳用于根据给定的时间粒度来计算当前的测试时间间隔。

第一个时间戳(例如T1)是2012年6月10日星期日,格林尼治标准时间+0300(格林尼治标准时间夏令时间),并且相关的测试时间间隔是:

  1. 一分钟内的所有秒数:
    [2012年6月10日,星期日,格林尼治标准时间+0300 14:25:00(格林尼治标准时间夏令时间)
    ,Sun Jun 10 2012 14:26:00 GMT + 0300(GTB Daylight Time))
  2. 一小时内的所有分钟:
    [2012年6月10日,星期日,格林尼治标准时间+0300(GTB夏令时间)
    ,Sun Jun 10 2012 15:00:00 GMT + 0300(GTB夏令时间))
  3. 一天中的所有小时:
    [2012年6月10日,星期日,格林尼治标准时间+0300(GTB夏令时间)
    ,2012年6月11日星期一03:00:00 GMT + 0300(GTB夏令时间))

冷数据库测试

最初的测试将在新启动的MongoDB实例上运行。 因此,在每次测试之间,我们将重新启动数据库,因此不会预加载任何索引。

类型

一分钟内

一小时内

一天中的几个小时

T1

0.02秒

0.097秒

1.771秒

T2

0.01秒

0.089秒

1.366秒

T3

0.02秒

0.089秒

1.216秒

T4

0.01秒

0.084秒

1.135秒

T4

0.02秒

0.082秒

1.078秒

平均

0.016秒

0.088秒

1.3132秒

我们将使用这些结果作为我将向您介绍的以下优化技术的参考。

热数据库测试

预热索引和数据是一种常见的技术,同时用于SQL和NoSQL数据库管理系统。 MongoDB为此提供了touch命令。 但这不是魔杖,您不要盲目使用它,以希望将所有性能问题都抛在后面。 滥用它会导致数据库性能急剧下降,因此请确保您了解数据及其用法。

touch命令让我们指定我们要预加载的内容:

  • 数据
  • 指标
  • 数据和索引

我们需要分析我们的数据大小以及如何查询它,以获得最佳的数据预加载。

数据大小占用空间

在分析数据时,MongoDB功能齐全。 嵌套,我们将使用以下命令对时间事件集合进行自省:

> db.randomData.dataSize()
3200000032
> db.randomData.totalIndexSize()
2717890448
> db.randomData.totalSize()
7133702032

数据大小约为3GB,而总大小几乎为7GB。 如果选择预加载所有数据和索引,我将达到当前工作站的8GB RAM限制。 这将导致交换,并且性能将下降。

弊大于利

为了复制这种情况,我将重新启动MongoDB服务器并运行以下命令:

db.runCommand({ touch: "randomData", data: true, index: true });

我在脚本文件中包含了此命令,以查看首次加载所有数据所需的时间。

D:\wrk\vladmihalcea\vladmihalcea.wordpress.com\mongodb-facts\aggregator\timeseries>mongo random touch_index_data.js
MongoDB shell version: 2.4.6
connecting to: random
Touch {data: true, index: true} took 15.897s

现在,让我们重新运行测试,看看这次我们得到了什么:

类型

一分钟内

一小时内

一天中的几个小时

T1

0.016秒

0.359秒

5.694秒

T2

0

0.343秒

5.336秒

T3

0.015秒

0.375秒

5.179秒

T4

0.01秒

0.359秒

5.351秒

T4

0.016秒

0.343秒

5.366秒

平均

0.009秒

0.355秒

5.385秒

性能急剧下降,我想包含此用例,以使您意识到优化是一项重要的业务。 您确实必须了解正在发生的事情,否则最终可能弊大于利。

这是此特定用例的内存使用情况的快照:

mongodb 时间范围查询 robo mongodb 时间序列_大数据

要查找有关此主题的更多信息,我建议花一些时间阅读有关MongoDB存储内部工作的信息。

仅预加载数据

如前所述,您需要了解两种可用的优化技术以及您的特定数据使用情况。 正如我们在上一篇文章中所解释的那样,在我们的项目中,我们仅在比赛阶段使用索引。 在数据获取期间,我们还将加载未索引的值。 因为数据大小完全适合RAM,所以我们可以选择仅预加载数据,而无需使用索引。

考虑到我们当前的集合索引,这是一个很好的选择:

"indexSizes" : {
      "_id_" : 1460021024,
      "created_on_1" : 1257869424
}

我们根本不需要_id索引,对于我们的特定用例,加载它实际上会影响性能。 因此,这次我们仅预加载数据。

db.runCommand({ touch: "randomData", data: true, index: false });
D:\wrk\vladmihalcea\vladmihalcea.wordpress.com\mongodb-facts\aggregator\timeseries>mongo random touch_data.j
MongoDB shell version: 2.4.6
connecting to: random
Touch {data: true} took 14.025s

重新运行所有测试将产生以下结果:

类型

一分钟内

一小时内

一天中的几个小时

T1

0

0.047秒

1.014秒

T2

0

0.047秒

0.968秒

T3

0.016秒

0.047秒

1.045秒

T4

0

0.047秒

0.983秒

T4

0

0.046秒

0.951秒

平均

0.003秒

0.046秒

0.992秒

这样比较好,因为我们可以看到所有三个时间间隔查询的改进。 但这不是我们能获得的最好,因为我们可以进一步改善它。

我们可以在后台流程中预加载所有工作集,这肯定会改善我们的所有聚合。

预加载工作集

为此,我编写了以下脚本:

load(pwd() + "/../../util/date_util.js");
load(pwd() + "/aggregate_base_report.js");
var minDate = new Date(Date.UTC(2012, 0, 1, 0, 0, 0, 0));
var maxDate = new Date(Date.UTC(2013, 0, 1, 0, 0, 0, 0));
var one_year_millis = (maxDate.getTime() - minDate.getTime());
aggregateData(minDate, maxDate, ONE_DAY_MILLIS);

这将汇总一年的数据跨度,并汇总一年中的每一天:

D:\wrk\vladmihalcea\vladmihalcea.wordpress.com\mongodb-facts\aggregator\timeseries>mongo random aggregate_year_report.js
MongoDB shell version: 2.4.6
connecting to: random
Aggregating from Sun Jan 01 2012 02:00:00 GMT+0200 (GTB Standard Time) to Tue Jan 01 2013 02:00:00 GMT+0200 (GTB Standard Time)
Aggregation took:299.666s
Fetched :366 documents.

到目前为止,重新运行所有测试可获得最佳结果:

类型

一分钟内

一小时内

一天中的几个小时

T1

0

0.032秒

0.905秒

T2

0

0.046秒

0.858秒

T3

0

0.047秒

0.952秒

T4

0

0.031秒

0.873秒

T4

0

0.047秒

0.858秒

平均

0

0.040秒

0.889秒

让我们检查一下当前的工作集内存占用量。

db.serverStatus( { workingSet: 1 } );
...
"workingSet" : {
        "note" : "thisIsAnEstimate",
        "pagesInMemory" : 1130387,
        "computationTimeMicros" : 253497,
        "overSeconds" : 723
}

这是一个估计,每个内存页面约为4k,因此我们的估计工作集约为4k * 1130387 = 4521548k = 4.31GB,确保我们当前的工作集适合我们的RAM。

工作集预加载和所有测试运行的内存使用情况也可以确认这种情况:

mongodb 时间范围查询 robo mongodb 时间序列_大数据_02

结论

将当前的每小时一分钟的结果与我以前的结果进行比较,我们可以看到已经有了五倍的改进,但是我们还没有完成。 这个简单的优化缩小了我以前的结果(0.209s)和JOOQ Oracle的结果(0.02s)之间的差距,尽管他们的结果仍然好一点。

我们得出结论,对于大型数据集,当前结构不利于我们。 我的下一篇文章将为您带来一种改进的压缩数据模型,该模型将使我们可以为每个分片存储更多文档。

  • 代码可在GitHub上获得 。



翻译自: https://www.javacodegeeks.com/2014/01/a-beginners-guide-to-mongodb-performance-turbocharging.html

mongodb指南