在MongoDB的MapReduce上踩過的坑


  太久沒動這里,目前人生處於一個新的開始。這次博客的內容很久前就想更新上來,但是一直沒找到合適的時間點(哈哈,其實就是懶),主要內容集中在使用Mongodb時的一些隱蔽的MapReduce問題:

  1、Reduce時的計數問題

  2、Reduce時的提取數據問題

  另外,補充一個小tips:mongoDB中建立的索引,優先使用固定的,而不要使用范圍。

 

一、MapReduce時的計數問題

   這個問題主要出現在使用“+1”的思路去計算累計次數時。如果在Map后的某一類中,記錄量過大,就會導致計數失敗。

  具體演示如下:

  原始數據(有400條一樣的存在數據庫results表中):{ "grade" : 1, "name" : "lekko", "score" : 95 }   

  進行MapReduce:

 1 db.runCommand({ mapreduce: "results", 
 2  map : function Map() {
 3     emit(
 4         {grade:this.grade},
 5         {recnum:1,score:this.score}
 6     );
 7 },
 8  reduce : function Reduce(key, values) {
 9     var reduced = {recnum:0,score:0};
10     values.forEach(function(val){
11             reduced.score += val.score;
12             ++reduced.recnum;
13         });
14     return reduced;
15 },
16 finalize : function Finalize(key, reduced) { 
17         return reduced;
18 },
19 out : { inline : 1 }
20 });

  滿懷希望地以為value.recnum會輸出400,結果卻是101!而value.scorce卻是輸出的正確的:38000(95*400)。本人在這疑惑了好久,並且通過更改reduce函數: function Reduce(key, values) { return {test:values}; } ,發現數據是這樣的:

  在原本Reduce函數中的forEach只遍歷了第一層的數據,即101個,所以++操作也只做了101次!

  經過思考,導致問題的原因關鍵就在於MapReduce中emit后的Bosn的數據格式,一個大於100的Array,會被拆分存儲,變成了非線性的鏈表結構,如圖:

  那么,分數相加卻能正確,可以大膽地推測:“reduced.score += val.score;” 語句可以智能地找到所有子結點的score並相加!

  最后,這里給出計數的替代方案,修改Reduce的++,改用+=操作:

1 function Reduce(key, values) {    ;
2     var reduced = {recnum:0,score:0};
3     values.forEach(function(val){
4             reduced.score += val.score;
5             reduced.recnum += val.recnum; 6         });
7     return reduced;
8 }


二、在Reduce中把數據提取出來組成Array

  

  這個問題產生的原因與上面的相似,也是由於emit后的數據在reduce時是非線性的(有層次關系),所以提取數據字段時也會產生問題,為了測試,往上面所說的表中再插入3條數據:

   { "grade" : 1, "name" : "monkey", "score" : 95 }, { "grade" : 2, "name" : "sudan", "score" : 95 }, { "grade" : 2, "name" : "xiaoyan", "score" : 95 } 

  編寫提取出各個grade的所有人名(不重復)列表:

 1 db.runCommand({ mapreduce: "results", 
 2  map : function Map() {
 3     emit(
 4         {grade:this.grade},
 5         {name:this.name}
 6     ); 
 7 },
 8  reduce : function Reduce(key, values) {
 9     var reduced = {names:[]};
10     values.forEach(function(val) {
11         var isExist = false;
12         for(var i = 0; i<reduced.names.length; i++) {
13             var cur = reduced.names[i];
14             if(cur==val.name){
15                 isExist = true;
16                 break;
17             }
18         }
19         if(!isExist)
20             reduced.names.push(val.name);
21     });
22     return reduced;
23 },
24  finalize : function Finalize(key, reduced) {
25     return reduced;
26 },
27  out : { inline : 1 }
28  });

  返回結果為:

1  { "_id" : {"grade" : 1},
2    "value" :{ "names" : [null,"lekko"]}
3  },
4  { "_id" : {"grade" : 2},
5    "value" :{ "names" : ["xiaoyan","sudan"]}
6  }

  新插入的grade=2的兩條數據正常了,但grade=1的monkey卻不見了!采用問題一的思維方式,肯定也是在Reduce時遍歷到一個數組對象,其name值為空,也給添加進來了,monkey對象根本就沒有訪問到。

  解決這一問題的方法是,拋棄MapReduce,改用Group:

 1 db.results.group({
 2  key : {"grade":true}, 
 3  initial : {names:[]}, 
 4  reduce : function Reduce(val, out) {
 5     var isExist = false;
 6     for(var i = 0; i<out.names.length; i++) {
 7         var cur = out.names[i];
 8         if(cur==val.name){
 9             isExist = true;
10             break;
11         }
12     }
13     if(!isExist)
14         out.names.push(val.name);    
15 }, 
16  finalize : function Finalize(out) {
17     return out;
18 }});

  這樣,便可正常取到grade=1時的name非重復集合!雖說MapReduce比Group要強大,速度也要快很多,但像這種要從大量項(超過100條)中提取數據,就有很大風險了。所以,使用MapReduce時,盡量只用到累加、累減、累乘等基本操作,不要去用++、push、delete等可能會產生風險的操作!

 

三、補充幾個小Tips

  1、使用Group或MapReduce時,如果一個分類只有一個元素,那么Reduce函數將不會執行,但Finalize函數還是會執行的。這時你要在Finalize函數中考慮一個元素與多個元素返回結果的一致性(比如,你把問題二中插入一個grade=3的數據看看,執行返回的grade=3時還有names集合嗎?)。

  2、查找范圍時的索引效率,如果查詢的是一個值的范圍,它索引的優先級是很低的。比如一個表test,有海量元素,字段有'committime'、'author',建立了兩個索引:author_1、committime:-1,author:1,下面的測試證明了效率:

    db.test.find({'committime':{'$gt':910713600000,'$lte':1410192000000},'author':'lekko'}).hint({committime:-1,author:1}).explain()   "millis" : 49163
    db.test.find({'committime':{'$gt':910713600000,'$lte':1410192000000},'author':'lekko'}).explain()  author_1                 "millis" : 2641

  轉載請注明原址:http://www.cnblogs.com/lekko/p/3963418.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM