V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
avatasia
V2EX  ›  问与答

有使用mongodb的进来聊聊了。关于mapreduce的问题

  •  
  •   avatasia · 2012-02-29 20:44:59 +08:00 · 4649 次点击
    这是一个创建于 4683 天前的主题,其中的信息可能已经有所发展或是发生改变。
    今天研究在mongodb里加js,然后在mapreduce里调用js。
    1. finalize里貌似不能使用外部定义的js,但是在finalize里可以自己写js, 例如扩展array。
    2. mapreduce的reduce会产生冗余数据,但是最后finalize的时候,冗余被处理掉了,想求原理。

    我做了个测试:
    m = function Map() {
    emit(this.PEPDATE, {TRANNUM: this.TRANNUM});
    }

    r = function Reduce(key, values) {
    var trannum = 0;
    var result = {TRANNUM: 0};
    values.forEach(function(v){
    trannum += v.TRANNUM;
    result.TRANNUM += v.TRANNUM;
    });
    log(key, trannum);
    return result;
    }

    f = function Finalize(key, reduced) {

    return reduced;
    }

    log的定义:
    function (i,j) {
    db.log.save({key:i, value: j});
    }

    log出来的结果
    key value
    20120130 5081
    20120130 3386
    20120130 3530
    20120130 3880
    汇总值是 15877

    finalize后的值是 15874, 这多余的3 怎么被处理掉了,求解惑。
    难道奥秘就在于 var result,这个mr全局变量么?
    21 条回复    1970-01-01 08:00:00 +08:00
    avatasia
        1
    avatasia  
    OP
       2012-02-29 21:00:05 +08:00
    又做了一遍测试,发现 trannum是没问题的。 15874,
    但是count有问题
    在reduce里 改成 log(key, values.length);
    结果是
    1000
    1001
    1001
    363

    实际是3362。

    而且奇怪的是,在mapreduce里 数组的长度是length, 而在shell里 用js,则是length(). 很纳闷为什么这样。
    avatasia
        2
    avatasia  
    OP
       2012-02-29 21:20:54 +08:00
    自己来回答。

    貌似mongodb reduce的条数是1000一组,然后每次把上次的结果算进来正好是1001。
    aligo
        3
    aligo  
       2012-02-29 21:41:38 +08:00
    这写法逻辑上有点问题,这假设了每次map都会经过reduce,但事实上并不是如此-A-
    所以,恩,你会丢掉3次- -
    avatasia
        4
    avatasia  
    OP
       2012-02-29 22:00:53 +08:00
    @aligo mapreduce里map和finalize都不能调用外部函数,例如db.log.save({a:1}), 但是reduce里是可以调用的。我感觉 mapreduce先调用 query获得一个结果集,在这个结果集用map去匹配数据,获得的数据用reduce去迭代。 可能map和finalize是使用各自的闭包,导致外部的function无法调用。
    aligo
        5
    aligo  
       2012-02-29 22:42:49 +08:00
    @avatasia
    假设你emit(A,{...})一次,emit(B,{...})三次
    那么只会调用3次reduce(B,{...}),reduce(A,{...})是不会被调用,因为它只有一条不需要reduce,而是直接进入到finalize阶段的

    我不明白你的需求是什么,所以我不知道有没有其他方式解决这个问题
    avatasia
        6
    avatasia  
    OP
       2012-03-01 09:15:22 +08:00
    @aligo 通过db.system.js.save(); 存入一个全局函数,然后在map,或 finalize里调用这个函数,例如我想在mr之后,在finalize里再去获得其他数据,合并到结果集里。
    aligo
        7
    aligo  
       2012-03-01 09:54:19 +08:00
    @avatasia 按照我的理解,mapreduce的主要意义在于以一种简单好理解的方式进行分布式的数据合并操作
    不可把map理解成迭代每一条数据,而finalize迭代每一条reduced结果
    因为和map和finalize可能是对于每一条数据的执行是分布式(没办法共享数据),而且可能是顺序未知或者并行(不能期望数据以排列好的方式fold),所以在我看来map和finalize不支持再去获取其他数据是理所应当的
    avatasia
        8
    avatasia  
    OP
       2012-03-01 10:13:19 +08:00
    @aligo map的emit 可以用js来构造key,那么这个步骤可以移出去做个function。 finalize,如果我想在汇总值上再加个对某个字段的distinct值,那么调用个外部function多好。还有有人提议,在mr的out参数加个document,可以直接把result的value输出成文档,不过这个应该还没在roadmap上,我觉的这个功能蛮好。
    aligo
        9
    aligo  
       2012-03-01 10:29:12 +08:00
    @avatasia

    map=>emit(this.country, {money: this.money, gender: [ this.gender ] })

    reduce=>
    var result = { money: 0, gender: [] }
    values.forEach( function( guy ){
    result.money += guy.money
    if ( !result.inArray( this.gender[0] ) ) {
    result.gender.push( this.gender[0] )
    }
    return result
    })
    avatasia
        10
    avatasia  
    OP
       2012-03-01 11:20:00 +08:00
    e = function(){
    return db.STATISDAY.count();
    }

    l = function(){
    db.log.save({t: new Date()});
    }
    m = function(){
    log();
    emit({PEPDATE:this.PEPDATE, xx:xx()}, {count: 1});
    }

    r = function(k, vs){
    log();
    var r = {count: 0};
    vs.forEach(function(v){
    r.count += v.count;
    })

    return {aa: xx(), count: r.count};
    }

    db.STATISDAY.mapReduce(m, r, {query:{PEPDATE: 20120130}, out:{inline:1}, scope:{xx:e, log: l}});

    在map里可以运行外部函数,但是只能读取db,不能写入db。貌似用了db.eval("map()").

    2. 以前格式化result的double类型,都是写个finalize,然后调用runcommand,现在可以在reduce里return,省略了finalize一步,这样就可以直接调用db.xx.mapReduce,不用写那么多代码了。
    avatasia
        11
    avatasia  
    OP
       2012-03-01 11:36:48 +08:00
    @aligo 嗯,这个方法不错,我试试跟我现在的性能比较一下。
    aligo
        12
    aligo  
       2012-03-01 11:52:24 +08:00
    @avatasia 需要注意的一点还有map时emit的第二个参数的格式,必须和reduce的返回值一致
    因为如果emit的第一个参数只有一次map,那么是不会经过reduce的
    avatasia
        13
    avatasia  
    OP
       2012-03-01 11:54:54 +08:00
    @aligo inArrary不是js默认支持的方法,我用我自己的算法,以前的耗时是57s,现在采用array.push, 54s.没多少提高。 distinct拆开做 是8 +10。
    avatasia
        14
    avatasia  
    OP
       2012-03-01 11:56:10 +08:00
    @aligo emit只有一次的话,value的Numeric值还是原来的格式,如果经过了reduce,就会变成double
    avatasia
        15
    avatasia  
    OP
       2012-03-01 12:03:36 +08:00
    @avatasia 你的写法应该是噩梦级别的,reduce里不能做inArray的操作,,我把distinct判断放到reduce里现在是2分钟07秒。
    avatasia
        16
    avatasia  
    OP
       2012-03-01 12:03:49 +08:00
    @aligo 你的写法应该是噩梦级别的,reduce里不能做inArray的操作,,我把distinct判断放到reduce里现在是2分钟07秒。
    avatasia
        17
    avatasia  
    OP
       2012-03-01 14:41:42 +08:00
    发现一个很悲剧的事情, 为了方便使用数据,我把里面的数字类型都转型为NumberLong,然后在做数组去重处理的时候。lastIndexOf不能识别重复的数据,写了个简单的测试 NumberLong(1) == NumberLong(1) , 返回 False。 所以以后如果是数字类型都用默认的类型 double存储,读取的时候再相应转换obj.toInt32() obj.toInt64(). 现在只能用1 * obj,这种方式转成double来处理。
    lainuo
        18
    lainuo  
       2012-03-01 14:51:49 +08:00
    多了3, 是因为mongo的mapreduce是incremental Map-reduce, 换言之, 就是这一次的reduce是跑在上一次的结果之上的.

    在reduce里作判断是一个很危险的事情,

    If you are trying to make a list of values unique in the reduce functions, you are probably doing it wrong.

    http://guide.couchdb.org/draft/views.html#example/3
    avatasia
        19
    avatasia  
    OP
       2012-03-01 15:26:08 +08:00
    @lainuo 这个自然考虑到了
    avatasia
        20
    avatasia  
    OP
       2012-03-01 15:34:09 +08:00
    在 finalize里 这样去重

    var ar = o.words;

    var l =ar.length;
    while(--l)
    {
    if(ar.lastIndexOf(1 * ar[l], l-1) > -1){
    ar.splice(l, 1);
    }
    }

    第一次测试 56692ms

    然后在循环之前 加上 ar.sort(), 设想可以减少 lastIndexOf和splice的时间损耗,结果是 56094ms,基本上没提高。去掉去重,看看。 13180ms。 去掉 array的 push, 9608。

    基本上mongodb没藏私。时间应该耗在lastIndexOf上。
    avatasia
        21
    avatasia  
    OP
       2012-03-01 15:41:32 +08:00
    去掉splice 53136.

    找了网上的一个数组去重算法

    http://www.webshowme.com/04js/content.asp?id=2350

    结果是 13707.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3075 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 13:09 · PVG 21:09 · LAX 05:09 · JFK 08:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.