说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

MongoDB Map Reduce速度提升20倍的优化宝典

HTML文档下载 WORD文档下载 PDF文档下载
MongoDB提供的Map Reduce非常灵活,对于大规模数据分析也相当实用。尽管MongoDB 2.4中Map Reduce有了大幅改进,但是相对来说其性能还是有很大的提升空间。本文就来尝试找出让Map Reduce速度最大化提升的方法。

自从MongoDB被越来越多的大型关键项目采用后,数据分析也成为了越来越重要的话题。人们似乎已经厌倦了使用不同的软件来进行分析(这都利用到了Hadoop),因为这些方法往往需要大规模的数据传输,而这些成本相当昂贵。

MongoDB提供了2种方式来对数据进行分析:Map Reduce(以下简称MR)和聚合框架(Aggregation Framework)。MR非常灵活且易于使用,它可以很好地与分片(sharding)结合使用,并允许大规模输出。尽管在MongoDB v2.4版本中,由于JavaScript引擎从Spider切换到了V8,使得MR的性能有了大幅改进,但是与Agg Framework(使用C++)相比,MR的速度还是显得比较慢。本文就来看看,有哪些方法可以让MR的速度有所提升。

测试

首先我们来做个测试,插入1000万文档,这些文档中包含了介于0和100万之间的单一整数值,这意味着,平均每10个文档具有相同的值。

> for (var i = 0; i < 10000000; ++i){ db.uniques.insert({ dim0: Math.floor(Math.random()*1000000) });}> db.uniques.findOne(){ "_id" : ObjectId("51d3c386acd412e22c188dec"), "dim0" : 570859 }> db.uniques.ensureIndex({dim0: 1})> db.uniques.stats(){        "ns" : "test.uniques",        "count" : 10000000,        "size" : 360000052,        "avgObjSize" : 36.0000052,        "storageSize" : 582864896,        "numExtents" : 18,        "nindexes" : 2,        "lastExtentSize" : 153874432,        "paddingFactor" : 1,        "systemFlags" : 1,        "userFlags" : 0,        "totalIndexSize" : 576040080,        "indexSizes" : {                "_id_" : 324456384,                "dim0_1" : 251583696        },        "ok" : 1}

这里我们想要得到文档中唯一值的计数,可以通过下面的MR任务来轻松完成:

> db.runCommand({ mapreduce: "uniques", map: function () { emit(this.dim0, 1); }, reduce: function (key, values) { return Array.sum(values); }, out: "mrout" }){        "result" : "mrout",        "timeMillis" : 1161960,        "counts" : {                "input" : 10000000,                "emit" : 10000000,                "reduce" : 1059138,                "output" : 999961        },        "ok" : 1}

正如你看到的,输出结果大约需要1200秒(在EC2 M3实例上测试),共输出了1千万maps、100万reduces、999961个文档。结果类似于:

> db.mrout.find(){ "_id" : 1, "value" : 10 }{ "_id" : 2, "value" : 5 }{ "_id" : 3, "value" : 6 }{ "_id" : 4, "value" : 10 }{ "_id" : 5, "value" : 9 }{ "_id" : 6, "value" : 12 }{ "_id" : 7, "value" : 5 }{ "_id" : 8, "value" : 16 }{ "_id" : 9, "value" : 10 }{ "_id" : 10, "value" : 13 }...

下面就来看看如何进行优化。

使用排序

我在之前的这篇文章中简要说明了使用排序对于MR的好处,这是一个鲜为人知的特性。在这种情况下,如果处理未排序的输入,意味着MR引擎将得到随机排序的值,

基本上没有机会在RAM中进行reduce,相反,它将不得不通过一个临时collection来将数据写回磁盘,然后按顺序读取并进行reduce。

下面来看看如果使用排序,会有什么帮助:

> db.runCommand({ mapreduce: "uniques", map: function () { emit(this.dim0, 1); }, reduce: function (key, values) { return Array.sum(values); }, out: "mrout", sort: {dim0: 1} }){        "result" : "mrout",        "timeMillis" : 192589,        "counts" : {                "input" : 10000000,                "emit" : 10000000,                "reduce" : 1000372,                "output" : 999961        },        "ok" : 1}

现在时间降到了192秒,速度提升了6倍。其实reduces的数量是差不多的,但是它们在被写入磁盘之前已经在RAM中完成了。

使用多线程

在MongoDB中,一个单一的MR任务并不能使用多线程——只有在多个任务中才能使用多线程。但是目前的多核CPU非常有利于在单一服务器上进行并行化工作,就像Hadoop。我们需要做的是,将输入数据分割成若干块,并为每个块分配一个MR任务。splitVector命令可以帮助你非常迅速地找到分割点,如果你有更简单的分割方法更好。

> db.runCommand({splitVector: "test.uniques", keyPattern: {dim0: 1}, maxChunkSizeBytes: 32000000}){    "timeMillis" : 6006,	"splitKeys" : [		{			"dim0" : 18171		},		{			"dim0" : 36378		},		{			"dim0" : 54528		},		{			"dim0" : 72717		},…		{			"dim0" : 963598		},		{			"dim0" : 981805		}	],	"ok" : 1}

从1千万文档中找出分割点,使用splitVector命令只需要大约5秒,这已经相当快了。所以,下面我们需要做的是找到一种方式来创建多个MR任务。从应用服务器方面来说,使用多线程和$gt / $lt查询命令会非常方便。从shell方面来说,可以使用ScopedThread对象,它的工作原理如下:

> var t = new ScopedThread(mapred, 963598, 981805)> t.start()> t.join()

现在我们可以放入一些JS代码,这些代码可以产生4个线程,下面来等待结果显示:

> var res = db.runCommand({splitVector: "test.uniques", keyPattern: {dim0: 1}, maxChunkSizeBytes: 32 *1024 * 1024 })> var keys = res.splitKeys> keys.length39> var mapred = function(min, max) { return db.runCommand({ mapreduce: "uniques", map: function () { emit(this.dim0, 1); }, reduce: function (key, values) { return Array.sum(values); }, out: "mrout" + min, sort: {dim0: 1}, query: { dim0: { $gte: min, $lt: max } } }) }> var numThreads = 4> var inc = Math.floor(keys.length / numThreads) + 1> threads = []; for (var i = 0; i < numThreads; ++i) { var min = (i == 0) ? 0 : keys[i * inc].dim0; var max = (i * inc + inc >= keys.length) ? MaxKey : keys[i * inc + inc].dim0 ; print("min:" + min + " max:" + max); var t = new ScopedThread(mapred, min, max); threads.push(t); t.start() }min:0 max:274736min:274736 max:524997min:524997 max:775025min:775025 max:{ "$maxKey" : 1 }connecting to: testconnecting to: testconnecting to: testconnecting to: test> for (var i in threads) { var t = threads[i]; t.join(); printjson(t.returnData()); }{         "result" : "mrout0",        "timeMillis" : 205790,        "counts" : {                "input" : 2750002,                "emit" : 2750002,                "reduce" : 274828,                "output" : 274723        },        "ok" : 1}{         "result" : "mrout274736",        "timeMillis" : 189868,        "counts" : {                "input" : 2500013,                "emit" : 2500013,                "reduce" : 250364,                "output" : 250255        },        "ok" : 1} {        "result" : "mrout524997",        "timeMillis" : 191449,        "counts" : {                "input" : 2500014,                "emit" : 2500014,                "reduce" : 250120,                "output" : 250019        },        "ok" : 1}{        "result" : "mrout775025",        "timeMillis" : 184945,        "counts" : {                "input" : 2249971,                "emit" : 2249971,                "reduce" : 225057,                "output" : 224964        },        "ok" : 1}

第1个线程所做的工作比其他的要多一点,但时间仍达到了190秒,这意味着多线程并没有比单线程快!

使用多个数据库

这里的问题是,线程之间存在太多锁争用。当锁时,MR不是非常无私(每1000次读取会进行yield)。由于MR任务做了大量写操作,线程之间结束时会等待彼此。由于MongoDB的每个数据库都有独立的锁,那么让我们来尝试为每个线程使用不同的输出数据库:

> var mapred = function(min, max) { return db.runCommand({ mapreduce: "uniques", map: function () { emit(this.dim0, 1); }, reduce: function (key, values) { return Array.sum(values); }, out: { replace: "mrout" + min, db: "mrdb" + min }, sort: {dim0: 1}, query: { dim0: { $gte: min, $lt: max } } }) }> threads = []; for (var i = 0; i < numThreads; ++i) { var min = (i == 0) ? 0 : keys[i * inc].dim0; var max = (i * inc + inc >= keys.length) ? MaxKey : keys[i * inc + inc].dim0 ; print("min:" + min + " max:" + max); var t = new ScopedThread(mapred, min, max); threads.push(t); t.start() }min:0 max:274736min:274736 max:524997min:524997 max:775025min:775025 max:{ "$maxKey" : 1 }connecting to: testconnecting to: testconnecting to: testconnecting to: test> for (var i in threads) { var t = threads[i]; t.join(); printjson(t.returnData()); }...{         "result" : {                "db" : "mrdb274736",                "collection" : "mrout274736"        },        "timeMillis" : 105821,        "counts" : {                "input" : 2500013,                "emit" : 2500013,                "reduce" : 250364,                "output" : 250255        },        "ok" : 1}...

所需时间减少到了100秒,这意味着与一个单独的线程相比,速度约提高2倍。尽管不如预期,但已经很不错了。在这里,我使用了4个核心,只提升了2倍,如果使用8核CPU,大约会提升4倍。

使用纯JavaScript模式

在线程之间分割输入数据时,有一些非常有趣的东西:每个线程只拥有约25万主键来输出,而不是100万。这意味着我们可以使用“纯JS模式”——通过jsMode:true来启用。开启后,MongoDB不会在JS和BSON之间反复转换,相反,它会从内部的一个50万主键的JS字典来reduces所有对象。下面来看看该操作是否对速度提升有帮助。

> var mapred = function(min, max) { return db.runCommand({ mapreduce: "uniques", map: function () { emit(this.dim0, 1); }, reduce: function (key, values) { return Array.sum(values); }, out: { replace: "mrout" + min, db: "mrdb" + min }, sort: {dim0: 1}, query: { dim0: { $gte: min, $lt: max } }, jsMode: true }) }> threads = []; for (var i = 0; i < numThreads; ++i) { var min = (i == 0) ? 0 : keys[i * inc].dim0; var max = (i * inc + inc >= keys.length) ? MaxKey : keys[i * inc + inc].dim0 ; print("min:" + min + " max:" + max); var t = new ScopedThread(mapred, min, max); threads.push(t); t.start() }min:0 max:274736min:274736 max:524997min:524997 max:775025min:775025 max:{ "$maxKey" : 1 }connecting to: testconnecting to: testconnecting to: testconnecting to: test> for (var i in threads) { var t = threads[i]; t.join(); printjson(t.returnData()); }...{         "result" : {                "db" : "mrdb274736",                "collection" : "mrout274736"        },        "timeMillis" : 70507,        "counts" : {                "input" : 2500013,                "emit" : 2500013,                "reduce" : 250156,                "output" : 250255        },        "ok" : 1}...

现在时间降低到70秒。看来jsMode确实有帮助,尤其是当对象有很多字段时。该示例中是一个单一的数字字段,不过仍然提升了30%。

MongoDB v2.6版本中的改进

在MongoDB v2.6版本的开发中,移除了一段关于在JS函数调用时的一个可选“args”参数的代码。该参数是不标准的,也不建议使用,它由于历史原因遗留了下来(见SERVER-4654)。让我们从Git库中pull最新的MongoDB并编译,然后再次运行测试用例:

...{         "result" : {                "db" : "mrdb274736",                "collection" : "mrout274736"        },        "timeMillis" : 62785,        "counts" : {                "input" : 2500013,                "emit" : 2500013,                "reduce" : 250156,                "output" : 250255        },        "ok" : 1}...

从结果来看,时间降低到了60秒,速度大约提升了10-15%。同时,这种更改也改善了JS引擎的整体堆消耗量。

结论

回头来看,对于同样的MR任务,与最开始时的1200秒相比,速度已经提升了20倍。这种优化应该适用于大多数情况,即使一些技巧效果不那么理想(比如使用多个输出dbs /集合)。但是这些技巧可以帮助人们来提升MR任务的速度,未来这些特性也许会更加易用——比如,这个ticket 将会使splitVector命令更加可用,这个ticket将会改进同一数据库中的多个MR任务。

英文原文:How to speed up MongoDB Map Reduce by 20x

2013中国智能交通与大数据技术峰会:迎接交通大数据新时代 大数据应用技术(上):加速创新,重塑世界 英特尔百度携手推出首个跨平台应用商店 12月9日:计算机软件第一夫人Grace Hopper出生 触摸屏将成笔记本标配 电脑行业或将迎来又一春 触摸4G,你必须要知道的那些事儿 TIOBE 2013年12月编程语言排行榜:Transact-SQL冲刺年度语言 敏捷开发的6个实战经验 【图文】走近华为企业业务与支撑万人技术团队的云数据中心 《习惯联盟》陈晓冬:与京东云合作让我们没有后顾之忧 流计算与批处理同现:畅享大数据时代的开源实践 挑战和机遇并存:大数据时代机器学习与可视化 【企业开源系列】收发一条Twitter推文的背后 在IE11中更快地获得您需要的内容 暴露程序员身份的8个行为 英特尔创新应用大赛大评审!创新也有中国风 苹果面临一问题:正源源不断地失去优秀工程师 初学编程者必知的五个网站 大数据基准测试专题论坛:基准测试是一把尚未完成的尺子 大数据应用(下):应用驱动技术,DaaS创造价值 大数据研究与发展专题论坛:大数据在各领域中的应用及发展策略 雅虎新目标:欲收购照片分享网站Imgur 移动开发工具访谈《近匠》第01期:七牛云存储—BaaS进化论 ShareSDK:A轮融资到位!将推社会化评论SDK 70美元,你也能用Raspberry Pi做个专属iBeacon基站 7个鲜为人知却超实用的PHP函数 从管理远程式团队和分布式团队中所获得的宝贵经验 Netflix开源数据流管理器Suro Google新创意:天花板加麦克风 人脑植芯片 苹果获弯曲屏新专利 可给手机弄个大波浪卷 真正的跨平台硬件测试:3DMark已打通PC、iOS、Android 请教用PHP开发物业收费软件的开行性! 谁有C++经典书籍电子版 VC技术内幕的电子版pdg文件格式要用何种工具才能浏览??? 请教各位:一个运行notes c++ api 的超级菜问题: 推荐一下制作GIF的免费软件. 关于双系统!!! 如何将数据库中保存的word文件读出并在IE中大开? 关于acmStreamOpen的问题? 关于editmask的问题 TURBOC 3.0的问题,在线等待..... 各位高手!我又一问题请教!请帮忙!!! IIS如何与Tomcat结合,请指教! 如何用java访问Excel文件? 如何添加空记录 非常着急!!请高手赐教!我刚注册,只有200分奉送!!谢谢!!! 哪儿可以提供ExpressNavBar vcl下? 各位高手快来指点一下,是关于动态创建组件的! 自己定义CMP的finder定位器的问题 四舍五入问题 请教牛人,能不能发一段java中有关计时的程序 一个小问题,请大侠指点,立即给分!!! 关于三角函数的问题 一个三百年前已忘得一干二净的问题 打包!!!问题问题。。。在线..... 一个c的问题!! [100分求救]在Canvas或Applet上画的图应该怎样输出为jpg图象呢? 用VC的console application 到底能编多大的程序? 关于程序在初始化时候注册窗口类的问题? 上网很慢 请问从log文件里都能得到哪些信息? 系统设计一般把数据表放一个数据库中还是分到多个数据库中? 树型控件的问题,PB8.03版本,大家遇到过吗? 强烈支持雍亲王倒分!!(江湖无赖) 怎样调用VC写的DLL 同步 兄弟们,我的rh8中怎么没有中文输入法? 在access中如何用函数?我怎么找不到函数库啊。那里有关于access的函数库教程? 请教 我装的DELPHI7.0不知为何在区域设置为“英文”是看到的汉字都成了“????”? <iframe>如何改变大小? 关于水晶报表的问题!(急!!!!)(在线等待~~~~~~~~) 关于vb脚本中运用三角函数的问题 一个三百年前已忘得一干二净的问题 cbc+数据库的问题????????? 能不能控制<input type='fele' ...>的文件类型,如不可输入(readonly全部都不能用了) 在线等待:谁有个最简单的加密算法? 谁有More Effective中文电子书! 请教一段java计时器的问题(送分了) 在ASP中能不能引用.net的框架组件,能的话该怎么做? 占便宜歌 不想周末加班了,帮我啊兄弟们! 小弟是2003年毕业生,想找个软件工程师工作,不知道有什么要求? 一个简单的添加记录语句,求解! 求一篇武则天的无字碑的1000字左右作文不胜感激~~~~~~~~~ 你读了自己的花是让别人看这篇文章的读后感.300字大部分要说人人为我,我为人人 已知2-根号7的整数部分是a,小数部分是b,试求b(ab+6)的值 有关月亮的比喻句必须是自己造的 could you please buy _____drinks and snacks?could you please buy _____drinks and snacks?sorry .I do not have _____money .A.some ,any B.any ,any C.any ,some any可用于疑问,为什么不能选, “殄”这个字怎么读? bec高级水平是什么水平啊 could you please buy( )snacks( )drinks?填 some and还是any or 上面是号字下面是食字 ,上面是一个殄字下面是一个食字.这两个字怎么念是什么意思 武则天简史(200字内) 根据首字母完成单词:-Could you please buy some drinks and snacks?-No,I____(不同意) 他这么说是啥含义阿 武则天的评价(200字左右)简单评价了武则天的功过 7. Who is the girl _____ wearing a red dress?A. whose B. that C. whom D. that’s7. Who is the girl _____ wearing a red dress?  A. whose   B. that   C. whom   D. that’s为什么这道题选D? "殄"这个字读什么? 武则天50字介绍 英语组词成句:girl,young,who,red,is,in,that,(.) 男的一般这么说是什么意思啊.女的 22:35:33 有事么. 男的 22:37:44 没事,跟你聊会儿天 女的 22:39:02 你是坐那谁旁边那个男孩子. 男的 22:41:44 哎!聪明!呵呵 女的 22:42:15 呵呵. 男的 22:43:57 今我们 为武则天写碑文 200字 十万火急! 什么水平才能考BEC高级?我的英语六级只考了500分,听力比较弱这个水平可以考BEC高级吗?还是考BEC中级比较好?考中级对找工作有用吗? 殄文怎么读 月亮的比喻句要新鲜的,不要晓夜,残月如死神之镰,将白惨惨的光洒在破败的墓地上月亮像一把冰冷的尖刀,插入游子的心.弯弯的月儿小小的船,小小的船儿两头尖.月亮象小船,弯弯的两头尖; 月 一个英语句子的困惑similarly,a king's servant might not be the best one to write a biography of that king,but a foreigner might not have the knowledge and sympathy necessary to write the king's biography-not for a readership from within the k 一个美女主动和我视频 然后我 问她我丑不丑 她说 这都不是事.是事就一阵.一阵就一会儿.一会儿就完事 BEC 高级 听力哪位有BEC高级 第四辑 听力的材料MP3什么的,能不能发给我, 萎靡的读音萎靡 的读音 关于暴殄天物的殄是殄,不是殓 第二大题怎么写 BEC高级听力多少分大概能拿A 已知3+根号7/2的整数部分是a,小数部分是b,求500a^2+(1+根号7)ab+4的值 怎么写第二大题 求BEC高级听力(2-4辑)! 《春夜喜雨》中的“花重锦官城”中的‘重’字的读音. 第二大题,怎么写 bec高级听力不过还有希望过吗我考了2010年的高级bec,但是听力指定过不了,还有希望过吗,有没有考过高级的亲身经历听力没过但高级过了的同学啊. “一帆风顺”中“帆”的读音到底是第一声还是第二声?“帆”的拼音只有一个第一声的,为什么在“一帆风顺”中“帆”字大多数人都是第二声? 关于月亮的比喻句5.6句就好,但要生动,要长(也不要太长了,长一点就好) 河南省高一化学必修一拔高题多多益善,若是超过5题得就给5分,10题10分.答案也要 风靡全球发音?是不是第三声?靡 读第二声是不是只是 奢靡,其他全部第三声 关于月亮的比喻句耐心回答一下吧! 第二大题怎么写, 已知根号10的整数部分是a,小数部分是b,求a²+b²(谢谢, 喷洒氧化乐果农药与怀孕 医生请进.我在喷洒氧化乐果农药后一个月内.(未采取任何安全措施)我爱人怀孕了.奔三的人了.盼望孩子已久.现在孩子已经快两个月了.真是非常的担心.望高人指 第二大题怎么写求解啊! who is the girl ( ) red 氧化乐果农药放置时间长了会失效么?我是说超过保质期以后.有研究的可以给我留言,.我是学化学的,. 求解第二大题.短文填空 who is the girl a red hat? 八年级物理问题(关于电热的)为什么串联电路中导体放出的热量与电阻成正比,而并联电路中导体放出的热量却与电阻成反比?不要用物理公式来解释,用原理来解释!(我想到脑都爆了还没想 ----Could you please help me carry the box?Its too heavy.----( )A.Sorry.I have no time.S.Its my p为什么不选A呢 who is the girl ( )red there?A./ B.over C.with D.in 物理w=qu怎样记牢? 月亮升上来了比喻句 Could you help me buy some____(many)drinks and snacks. 我眼中的武则天1000字求求你们了!发发慈悲吧!再不告我就死翘了!谢谢了 BEC中级和6级哪个难 could you buy some drinks为什么用some,不是用any吗 一篇关于武则天的论文1000字 种出来的花上面有字看到在蛋里浇水能种出有字的花.不知道怎么种的.有谁在种给我联系好吗?先,提供可靠情报者,追加500分再说, 一个让人很困惑的英语句子---务必高手进As the bus began coming near the school I wondered what my teacher would be like .我还看在网上看到过这样的句子:As the bus began coming near to the school I wondered what my teacher
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘