阅读更多
自从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.length
39
> 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:274736
min:274736 max:524997
min:524997 max:775025
min:775025 max:{ "$maxKey" : 1 }
connecting to: test
connecting to: test
connecting to: test
connecting 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:274736
min:274736 max:524997
min:524997 max:775025
min:775025 max:{ "$maxKey" : 1 }
connecting to: test
connecting to: test
connecting to: test
connecting 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:274736
min:274736 max:524997
min:524997 max:775025
min:775025 max:{ "$maxKey" : 1 }
connecting to: test
connecting to: test
connecting to: test
connecting 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
13
8
评论 共 3 条 请登录后发表评论
3 楼 lord_is_layuping 2013-08-14 14:25
收藏      
2 楼 kenshinyelin 2013-07-09 16:33
果断收藏   
1 楼 youjianbo_han_87 2013-07-09 13:16
牛叉。   

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • MongoDB MapReduce速度提升20倍的优化宝典

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

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

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

  • 面试宝典之MongoDB

    什么是MongoDB MongoDB是一款由C++编写的跨平台、面向文档的非关系型数据库。是非关系型数据库当中功能最丰富、最向关系型数据库的产品。他支持的数据结构非常松散,是类似JSON的BSON格式,可以存储比较复杂的的...

  • PHP 十年程序员 面试宝典

    Nginx 是一个开源的” 高性能代理服务器 (可以处理数千个并发且迅速响应)”,采用异步非阻塞的事件驱动模型实现了高可用(高性能、低消耗、可靠稳定)。常用于 Web 服务器、负载均衡、反向代理以及静态资源缓存等。...

  • MongoDB管理与开发精要

    MongoDB管理与开发精要 ... 最初,本书的部分初稿在几大IT技术社区“疯传”,被社区网友视为学习MongoDB的“宝典”。由于受到社区网友的热捧,在初稿的基础上,作者对本书内容进行了系统化的补充,使原...

  • 【java毕业设计】智慧社区教育服务门户.zip

    有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上

  • 基于selenium的携程机票爬虫资料齐全+详细文档+高分项目+源码.zip

    【资源说明】 基于selenium的携程机票爬虫资料齐全+详细文档+高分项目+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

  • 【java毕业设计】智慧社区宠物管理系统(源代码+论文+PPT模板).zip

    zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。

  • 免费下载:Civil War (Stuart Moore)_xVBgd.zip

    免费下载:Civil War (Stuart Moore)_xVBgd.zip

  • JSP基于WEB的图书馆借阅系统的设计与实现(源代码+论文)(2024pw).7z

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于计算机科学与技术等相关专业,更为适合;

  • 免费下载:Spider-Man (Stefan Petrucha)_2pBuA.zip

    免费下载:Spider-Man (Stefan Petrucha)_2pBuA.zip

  • jj视频合并程序代码QZQ.txt

    jj视频合并程序代码QZQ

  • 【java毕业设计】智慧社区会员等级提升系统(源代码+论文+PPT模板).zip

    zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。

  • jsp医院病区管理系统(论文+中期检查表+任务书+综合材料)(20242g).7z

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于计算机科学与技术等相关专业,更为适合;

  • 基于卷积神经网络的通信调制方式识别详细文档+全部资料+优秀项目+源码.zip

    【资源说明】 基于卷积神经网络的通信调制方式识别详细文档+全部资料+优秀项目+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

  • 计算机网络学习之旅.pptx

    理层、数据链路层、网络层、传输层和应用层的功能、技术和协议。同时,对网络安全、无线网络、网络管理和网络新技术进行了深入剖析。通过案例分析帮助学习者更好地理解实际应用,推荐学习资源助力深入学习。最后总结要点并展望未来网络发展趋势,为计算机网络学习者提供了丰富的知识和实用的指导。

  • 51单片机控制的智能小车.7z

    51单片机控制的智能小车.7z

  • 计算结构体变量的内存大小1.cpp

    计算结构体变量的内存大小1.cpp

Global site tag (gtag.js) - Google Analytics