一个初学者的指南—MongoDB的性能优化 简介

这是MongoDB时间系列的第二部分教程,这篇文章将致力于性能调优的讲解。在我的前一篇文章中,提到了我们的虚拟化项目需求。总之,我们有5000万次的事件,从2012年1月持续到2013年1月,使用下面这个结构进行统计:

 

{

“_id” : ObjectId(“52cb898bed4bd6c24ae06a9e”),

“created_on” : ISODate(“2012-11-02T01:23:54.010Z”)

“value” : 0.19186609564349055

}

我们要统计最小值,最大值,和平均值以及条目数,离散时间样本如下:

all seconds in a minute

all minutes in an hour

all hours in a day

这是基本测试脚本:

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)Sun Jun 10 2012 14:25:00 GMT(格林尼治标准时间)+0300 (GTB Daylight Time)(GTB夏令时)和相关的测试时间间隔如下:

all seconds in a minute:
[ Sun Jun 10 2012 14:25:00 GMT+0300 (GTB Daylight Time)
, Sun Jun 10 2012 14:26:00 GMT+0300 (GTB Daylight Time) )

all minutes in an hour:
[ Sun Jun 10 2012 14:00:00 GMT+0300 (GTB Daylight Time)
, Sun Jun 10 2012 15:00:00 GMT+0300 (GTB Daylight Time) )

all hours in a day:
[ Sun Jun 10 2012 03:00:00 GMT+0300 (GTB Daylight Time)
, Mon Jun 11 2012 03:00:00 GMT+0300 (GTB Daylight Time) )

 

 

冷数据库测试

第一个测试时将运行在一个刚打开的mongoDB实例上。每个测试之间我们要重新启动数据库,所以没有索引会预加载。

我们将使用这个结果作为下一个优化技术的参考

精彩马上呈现!

 

热数据库测试

预加载索引和数据是一项常见的技术,被应用在sql和nosql管理的数据库系统中。

MongoDB为实现此目的提供了相关的命令。但是它不是万能的,不能盲目使用它去解决性能落后的问题,否则会使你数据库的性能急剧下降,所以你必须了解你的数据库并且熟练的使用它。

这些相关的指令可以让我们指定预先加载的项:

==数据

==索引

==数据和索引

我们需要分析数据的大小和我们要怎么去查询,以得到最好的预加载的数据。

 

 

 

数据大小的足迹

MongoDB在分析数据方面功能强大。接下来,我们用下面的命令来检查时间集合事件。

> db.randomData.dataSize()

3200000032

> db.randomData.totalIndexSize()

2717890448

> db.randomData.totalSize()

7133702032

 

当总规模为7GB的时候,数据大小大约为3GB。我做了一个测试,如果选择预先加载所有的数据和索引,就会打破当前工作站的8GB内存的限制,这会使swap分区和性能大大降低。 

 

 

弊大于利

复制这个场景需要重新启动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

现在重新做这个测试,看看会得到什么结果

可以看到性能大幅下降,通过这个例子你要意识到优化是一件很严肃的事情。你必须明白你在做什么,否则弊大于利。

这是这个特定的使用情况下,快照的内存使用情况

 

 

如果想了解更多这个方面的,我建议你花点时间读一些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.jMongoDB shell version: 2.4.6connecting to: random

Touch {data: true} took 14.025s

重新运行所有的测试,产生的结果如下:

 

可以看出这次的查询是三次中比较好的。但这并不是最好的,我们还可以进一步的改善。

我们可以编写一个预加载工作集,设置为一个后台进程,这种方法会显著的提高性能。

预加载工作集的脚本如下:

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.

重新运行所有的测试会看到最好的结果:

这时可以检查一下当前工作集的内存使用情况

db.serverStatus( { workingSet: 1 } );

“workingSet” : {

“note” : “thisIsAnEstimate”,

“pagesInMemory” : 1130387,

“computationTimeMicros” : 253497,

“overSeconds” : 723

 

这是一个估计,每个内存页是4k,所以我们估计工作集大约是4 k * 1130387 = 1130387 k = 4.31 gb,,确保了我们当前工作集适合RAM。

预加载和运行的测试对内存的使用也证实了这种情况

总结

和我的前一篇文章的数据比较,在minutes-in-hour 中已经有了五个时间的减少,但是我们还没有完成它。通过这个简单的优化,可以看出它减少了我之前的0.209s和JOOQ Oracle0.02秒的差距。尽管他们的结果已经非常不错了。

我们的结论是,目前的结构不利于我们对大型数据集。我的下一篇文章

将为你带来一种改进压缩数据模型的方法,这将允许我们每个分片存储更多的文件碎片。

代码可以在GitHub 中找到

如果你喜欢我的文章,并且期待及时得到我最新帖子的邮件通知,你只需关注我的博客。

 

 

Comment

*

沪ICP备14014813号-2

沪公网安备 31010802001379号