1
avatasia OP 又做了一遍测试,发现 trannum是没问题的。 15874,
但是count有问题 在reduce里 改成 log(key, values.length); 结果是 1000 1001 1001 363 实际是3362。 而且奇怪的是,在mapreduce里 数组的长度是length, 而在shell里 用js,则是length(). 很纳闷为什么这样。 |
2
avatasia OP 自己来回答。
貌似mongodb reduce的条数是1000一组,然后每次把上次的结果算进来正好是1001。 |
3
aligo 2012-02-29 21:41:38 +08:00
这写法逻辑上有点问题,这假设了每次map都会经过reduce,但事实上并不是如此-A-
所以,恩,你会丢掉3次- - |
4
avatasia OP @aligo mapreduce里map和finalize都不能调用外部函数,例如db.log.save({a:1}), 但是reduce里是可以调用的。我感觉 mapreduce先调用 query获得一个结果集,在这个结果集用map去匹配数据,获得的数据用reduce去迭代。 可能map和finalize是使用各自的闭包,导致外部的function无法调用。
|
5
aligo 2012-02-29 22:42:49 +08:00
@avatasia
假设你emit(A,{...})一次,emit(B,{...})三次 那么只会调用3次reduce(B,{...}),reduce(A,{...})是不会被调用,因为它只有一条不需要reduce,而是直接进入到finalize阶段的 我不明白你的需求是什么,所以我不知道有没有其他方式解决这个问题 |
6
avatasia OP @aligo 通过db.system.js.save(); 存入一个全局函数,然后在map,或 finalize里调用这个函数,例如我想在mr之后,在finalize里再去获得其他数据,合并到结果集里。
|
7
aligo 2012-03-01 09:54:19 +08:00
@avatasia 按照我的理解,mapreduce的主要意义在于以一种简单好理解的方式进行分布式的数据合并操作
不可把map理解成迭代每一条数据,而finalize迭代每一条reduced结果 因为和map和finalize可能是对于每一条数据的执行是分布式(没办法共享数据),而且可能是顺序未知或者并行(不能期望数据以排列好的方式fold),所以在我看来map和finalize不支持再去获取其他数据是理所应当的 |
8
avatasia OP @aligo map的emit 可以用js来构造key,那么这个步骤可以移出去做个function。 finalize,如果我想在汇总值上再加个对某个字段的distinct值,那么调用个外部function多好。还有有人提议,在mr的out参数加个document,可以直接把result的value输出成文档,不过这个应该还没在roadmap上,我觉的这个功能蛮好。
|
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 }) |
10
avatasia OP 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,不用写那么多代码了。 |
12
aligo 2012-03-01 11:52:24 +08:00
@avatasia 需要注意的一点还有map时emit的第二个参数的格式,必须和reduce的返回值一致
因为如果emit的第一个参数只有一次map,那么是不会经过reduce的 |
13
avatasia OP @aligo inArrary不是js默认支持的方法,我用我自己的算法,以前的耗时是57s,现在采用array.push, 54s.没多少提高。 distinct拆开做 是8 +10。
|
14
avatasia OP @aligo emit只有一次的话,value的Numeric值还是原来的格式,如果经过了reduce,就会变成double
|
15
avatasia OP @avatasia 你的写法应该是噩梦级别的,reduce里不能做inArray的操作,,我把distinct判断放到reduce里现在是2分钟07秒。
|
16
avatasia OP @aligo 你的写法应该是噩梦级别的,reduce里不能做inArray的操作,,我把distinct判断放到reduce里现在是2分钟07秒。
|
17
avatasia OP 发现一个很悲剧的事情, 为了方便使用数据,我把里面的数字类型都转型为NumberLong,然后在做数组去重处理的时候。lastIndexOf不能识别重复的数据,写了个简单的测试 NumberLong(1) == NumberLong(1) , 返回 False。 所以以后如果是数字类型都用默认的类型 double存储,读取的时候再相应转换obj.toInt32() obj.toInt64(). 现在只能用1 * obj,这种方式转成double来处理。
|
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 |
20
avatasia OP 在 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上。 |
21
avatasia OP |