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

2012-02-29 20:44:59 +08:00
 avatasia
今天研究在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全局变量么?
4648 次点击
所在节点    问与答
21 条回复
avatasia
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
2012-02-29 21:20:54 +08:00
自己来回答。

貌似mongodb reduce的条数是1000一组,然后每次把上次的结果算进来正好是1001。
aligo
2012-02-29 21:41:38 +08:00
这写法逻辑上有点问题,这假设了每次map都会经过reduce,但事实上并不是如此-A-
所以,恩,你会丢掉3次- -
avatasia
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
2012-02-29 22:42:49 +08:00
@avatasia
假设你emit(A,{...})一次,emit(B,{...})三次
那么只会调用3次reduce(B,{...}),reduce(A,{...})是不会被调用,因为它只有一条不需要reduce,而是直接进入到finalize阶段的

我不明白你的需求是什么,所以我不知道有没有其他方式解决这个问题
avatasia
2012-03-01 09:15:22 +08:00
@aligo 通过db.system.js.save(); 存入一个全局函数,然后在map,或 finalize里调用这个函数,例如我想在mr之后,在finalize里再去获得其他数据,合并到结果集里。
aligo
2012-03-01 09:54:19 +08:00
@avatasia 按照我的理解,mapreduce的主要意义在于以一种简单好理解的方式进行分布式的数据合并操作
不可把map理解成迭代每一条数据,而finalize迭代每一条reduced结果
因为和map和finalize可能是对于每一条数据的执行是分布式(没办法共享数据),而且可能是顺序未知或者并行(不能期望数据以排列好的方式fold),所以在我看来map和finalize不支持再去获取其他数据是理所应当的
avatasia
2012-03-01 10:13:19 +08:00
@aligo map的emit 可以用js来构造key,那么这个步骤可以移出去做个function。 finalize,如果我想在汇总值上再加个对某个字段的distinct值,那么调用个外部function多好。还有有人提议,在mr的out参数加个document,可以直接把result的value输出成文档,不过这个应该还没在roadmap上,我觉的这个功能蛮好。
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
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
2012-03-01 11:36:48 +08:00
@aligo 嗯,这个方法不错,我试试跟我现在的性能比较一下。
aligo
2012-03-01 11:52:24 +08:00
@avatasia 需要注意的一点还有map时emit的第二个参数的格式,必须和reduce的返回值一致
因为如果emit的第一个参数只有一次map,那么是不会经过reduce的
avatasia
2012-03-01 11:54:54 +08:00
@aligo inArrary不是js默认支持的方法,我用我自己的算法,以前的耗时是57s,现在采用array.push, 54s.没多少提高。 distinct拆开做 是8 +10。
avatasia
2012-03-01 11:56:10 +08:00
@aligo emit只有一次的话,value的Numeric值还是原来的格式,如果经过了reduce,就会变成double
avatasia
2012-03-01 12:03:36 +08:00
@avatasia 你的写法应该是噩梦级别的,reduce里不能做inArray的操作,,我把distinct判断放到reduce里现在是2分钟07秒。
avatasia
2012-03-01 12:03:49 +08:00
@aligo 你的写法应该是噩梦级别的,reduce里不能做inArray的操作,,我把distinct判断放到reduce里现在是2分钟07秒。
avatasia
2012-03-01 14:41:42 +08:00
发现一个很悲剧的事情, 为了方便使用数据,我把里面的数字类型都转型为NumberLong,然后在做数组去重处理的时候。lastIndexOf不能识别重复的数据,写了个简单的测试 NumberLong(1) == NumberLong(1) , 返回 False。 所以以后如果是数字类型都用默认的类型 double存储,读取的时候再相应转换obj.toInt32() obj.toInt64(). 现在只能用1 * obj,这种方式转成double来处理。
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
2012-03-01 15:26:08 +08:00
@lainuo 这个自然考虑到了
avatasia
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上。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/28449

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX