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

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

VB创建数据驱动窗体 得到鼠标位置 -VB资料 动态加入控件到VB控件数组中 对ListView中的列排序-VB资料 放一个Combo到Toolbar中 -VB资料 改进VB的驱动器列表框 给TreeView控件添加弹式菜单 -VB资料 计算从开机到现在用了多少秒-VB资料 将立体的ListView标题栏转换成平面式的标题栏 -VB资料 禁用窗体上所有控件 -VB资料 拷贝或移动列表框或组合框中的内容-VB资料 利用VB5开发ActiveX控件 利用VB三维面板控件设计流动条 浅谈用VB6.0编写BO程序 巧用Visual Basic的TIMER控件 VB取得控件绝对Top值 VB如何解决VB中的Grid 控件的打印问题 VB如果自定义 MsgBox 的按钮,标题等 VB如何创建数据绑定控件。 VB如何解决VB中的Grid控件的打印问题 VB如何强制文件对话框再次读取正确的磁盘? VB如何取得RichTextBox控件中光标所在行的值? VB如何设定 MsgBox 在若干时间之后若无回应則自动关闭? VB如何使用VB控件Commom Dialog VB如何用VB6写COM组件(二) VB如何用VB6写COM组件(一) VB如何在VB中使用Delphi的控件 VB如何在运行时动态增加控件 显示和隐藏鼠标-VB资料 VB用ActiveX Control Pad VB用API函数改进ListView 控件的显示效果 大侠给几篇C程序原代码 到底要怎样才能用好progressbar进度条? 参与者有分! 怎样调用 其它窗体的内的对象的属性?? JSP的应用经验 工具条的问题,求解 我喜欢西瓜小姐 我公司现有固定电话收发电子邮件系统,可收附件,将WORD,HTML文档转换为TXT文本 关于内码问题 远程控制 关于JDBC2.0的问题(50分!!!!) 哪位有高招? 升级BIOS和升级主板? 急!急!急!急!窗口不能关闭. 哥哥姐姐们请帮忙???(急需linux环境下用php+mysql如何实现搜索引擎) 大家赞不赞成开一个Palm技术区?赞成的跟贴,满50个就开。 定义 Dim reader As System.Data.SqlClient.SqlDataReader lvlvlvlylyly请看,你的贴子我加不上了,还查一段程序加在这里了 如何传送monthview1中用户选择的日期到其他窗体里 你该怎么做呢 国内什么样的网站托管好?还是选择国外的? 女网友一定要小心 mschart控件问题 赶快来尝试一下吧! 如何在 View 类中编写press any key to continue功能模块 Vc++下应用Matlab数学函数库出错 如何用VB实现自动登录? 赶快来尝试一下吧! ado的问题! 如何传递带“%”的参数? 有全套的ERP源代码为你提供,助你在开发过程中更上一层楼 字符串str=" aaa ",如何变为"aaa" 怎么没有人理俺泥?? 关于dw的问题 如何读入数据库中的HTML格式的正文呢? 一个在阅读<<深入浅出MFC>>中遇到的问题 !!!!急 新手,说找不到mfc42d.dll文件 怎么没有人理俺泥?? 关于dw的问题 FAST.lib 2000 for windows是个什么东东 请问各位一个非常简单的问题 jdk和jvm 笨蛋 大家有什么建议吗? ASP.net如何刷新另一个页面 有一年delphi工作经验的程序员在全国各地的待遇如何?哪个城市有发展前途(钱途)? 100分求解!!!这个问题大家都来看,来者有分!!! 告诉我几个地方,给你几十分,简单吧 :-) 有没有关于VPN的书籍 Interbase源码分析 西瓜是Plmm哎~! 一个字符串替换的小问题。。。 还有一个问题需要问大家。。。特别急。。。 我变成了大家的一员 吃蛋白质多尿液中尿素含量高吗 鱼塘亚硝酸盐高怎么办 将等物质的量的Na,Na2O,Na2O2投入大量且相等的水中,所得溶液的质量分数分别为a,b,c(忽略溶液体积变化),其关系是 为什么尿素可使蛋白质变性,尿中存在尿素为什么肾脏不变性? 如何用甲醛法制备三氯甲烷 钠的氧化物有Na2O2和Na2O两种这两种化合物组成混合物,钠与氧的质量比为23:12求Na2O2和Na2O的物质的量之 1、正常的尿液中含有 ( ) A、水和蛋白质 B、水和大量的葡萄糖 C、尿素和蛋白质 D、尿素和水 2、尿的形成 家庭怎么制三氯甲烷和如何检验? 物质量均为nmolNa,Na2O,Na2O2,同时加入nL水中完全反应后测溶液密度为ag/cm3则所测得溶液的物质的量浓度为? 是不是任何物质在水中都有一定溶解度 氯仿如何制甲酸最好有化学反应式 1,在基因型前面加的分数是什么意思?(例如1/3 AaBb )2,分枝法怎么推测子代性状分离比?子一代自交.3,推测基因型和表现型有没有什么诀窍,请举例.加扣扣, 南美白对虾养殖 如何降解亚硝酸盐?什么引起亚硝酸盐偏高? 河水中,亚硝酸盐跟氨氮的关系? 下列物质中不能与二氧化碳反应的是 A氧气 B水 C木炭 D氢氧化钙 那么C呢~ 南美白对虾养殖水中亚硝酸盐过高如何降解 怎么测污水中的氨氮和亚硝酸盐?有仪器氨氮比色计亚硝酸盐比色计,这个直接测出吸光度是吗?怎么在测出浓度 质遗传的杂交实验,F1的表现型总是和母本一样这句话错在哪 水产养殖水质处理剂有哪些? 冰片怎么溶化 怎样学习我很难理解概念,总觉得很模糊.每个知识点很难联系在一起.我对遗传学比较有兴趣,但是感觉很难.我花了很多时间去了解、记忆, 给人体内注射酒精有什么后果?如题. 怎样快速去除养殖水体的氨氮,亚硝酸盐? 生物遗传现象简介 ETFE材料的化学式,即四氟乙烯的化学式是什么 亚硝酸盐,氨氮都是正常,为什么PH值会过高呢?HP值在9.2虾放里水里面后3-4个小时就全部都死光了,请问这个问题怎么决解,是什么原因呢? Aa与Aa自交生出AA:Aa:aa=1:2:1为什么有时算Aa的概率=2/3为什么有时候概率是1/2什么时候分母4为什么时候分母为3哈 北京奥运会体育场馆“鸟巢”在顶部铺设的是两层膜:ETFE膜和PTFE膜,外层是用来挡雨的ETFE膜,ETFE膜下12m的位置,将铺设用于吸音的普通吸音板和PTFE膜,其作用都是用来减弱“回声”的.那么它们 氯化钠是熔点是多少?为什么 血液和尿液中同时含有蛋白质是什么原因 ETFE是什么材料 我的海水缸no2 no3都偏高,怎么办? 为什么第一题第一个空不是AA呢\?第二题为什么没有AABb?还有,计算怎么算?AA aa AaBb AaBB 20/27 为什么鱼塘里的水亚硝酸盐的量越来越高如何降底? 海水中的亚硝酸盐含量受温度影响大吗?家里有养殖海参的,在温室里,温度有十八度左右.本来海水亚硝酸盐含量符合标准(海水温度5度左右),可放到温室池子里浓度就超标了,懂化学和水产的 关于基因遗传某女性患红绿色盲,她的下列亲人中肯定携带色盲基因的是A 祖父 B 外祖父 C 外祖母 D 祖母求详解 ETFE塑料介绍? 降低氯化钠熔点怎么降低?还有,电解时生成钠,但钠很活泼,能与空气反应,那在电解时应注意什么,才不会让刚生成的钠又与空气反应? 如果父母双方都有某病,那么无论孩子男女都会患比病,如果父母双方有一个人有此病,那么生出的女孩百分百患病,男孩百分之五十患病,我想问一下这种病是什么遗传病?好久没做过生物遗传题 鱼塘水亚硝酸盐超标,该怎么办?鱼塘亚硝酸盐超标,怎么把亚硝酸盐的含量降下来?换水除外,我主要是问泼洒什么药物? 遗传变异对生物本身的意义是什么?A .能培育出新的品种B.能保持前后代染色体数目恒定C.能把性状传给后代D.既能保持种族的延续,又能使后代适应变化了的环境 1.假定某三体植株自交时,n+1的胚囊全部参与受精,而参与受精的花粉中,n+1的花粉只有10%。那么,该三体植株自交子代群体中,四体、三体和正常二倍体个体数分别是?5%、50%、45%)请问为 氯仿是什么哦?分子式以及它的空间结构 生物遗传特性和变异特性的意义是什么? 【急求解】一道高中生物题,遗传方面的一株纯黄玉米(YY)与一株纯白玉米(yy)相互传粉,两株植株结出的种子的胚和胚乳的基因情况是:A、胚细胞相同、胚乳细胞不同B、胚细胞和胚乳细胞都 冰片则么融化在中药里?有没有不用酒精那些的 因为当时医生只说了在煎好中药之后放进去 生物遗传与变异的意义, 打针时进了空气会不会死?不进血管也会死吗?多久会死? 鱼池水亚硝酸高怎么办? 请问蒸发器的作用是什么? 人的身体进多少空气会死 如何融解冰片 谈谈遗传的生物学意义. 为什么将空气注入静脉会导致人体死亡呢? 鱼塘的亚硝酸盐高怎样才能降低? 冰箱里蒸发器的作用是什么请简明扼要 什么叫往身体注射空气? 高中生物题--DNA及基因突变(1)下面哪种酶在遗传信息的表达和传递中不起作用A DNA连接酶 B DNA聚合酶 C RNA聚合酶 D解旋酶(2)镰刀型贫血症的病因是DNA分子的一条链上的碱基CTT变成CAT,这个DNA分子 怎样把冰片溶解到白凡士林中? x克Na2O和y克的Na2O2分别加入等质量的水中(水足量),结果得到等质量的溶液,则x和y的关系.比大小x克Na2O和y克的Na2O2分别加入等质量的水中(水足量),结果得到等质量分数的溶液,则x和y的关系
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘