2012-05-06 6 views
4

MongoDBでの集計については多くの質問がありますが、まだ完全な解決策は見つかりませんでした。MongoDBマップによる一意の値の集約

ここに私のデータの例です:

{ 
    "fruits" : { 
     "apple" : "red", 
     "orange" : "orange", 
     "plum" : "purple" 
    } 
} 
{ 
    "fruits" : { 
     "apple" : "green", 
     "plum" : "purple" 
    } 
} 
{ 
    "fruits" : { 
     "apple" : "red", 
     "orange" : "yellow", 
     "plum" : "purple" 
    } 
} 

は今、私の目標は、それぞれの果実のための各色の人気を決定することであるので、このようなものは、出力のコレクションのようになります。

{ 
    "_id" : "apple" 
    "values" : { 
     "red" : 2, 
     "green" : 1 
    } 
} 
{ 
    "_id" : "orange" 
    "values" : { 
     "orange" : 1, 
     "yellow" : 1 
    } 
} 
{ 
    "_id" : "plum" 
    "values" : { 
     "purple" : 3 
    } 
} 

私は様々なM/R機能を試していますが、最終的には機能しないか、指数関数的に長くなります。この例(果物)の文脈では、私は約1,000,000種類の果物と約100,000色のトータル文書を持っています。私の現在の作業M/Rはこれです:

map = function() { 
    if (!this.fruits) return; 
    for (var fruit in this.fruits) { 
     emit(fruit, { 
      val_array: [ 
       {value: this.fruits[fruit], count: 1} 
      ] 
     }); 
    } 
}; 

reduce = function(key, values) { 
    var collection = { 
     val_array: [] 
    }; 
    var found = false; 
    values.forEach(function(map_obj) { 
     map_obj.val_array.forEach(function(value_obj) { 
      found = false; 
      // if exists in collection, inc, else add 
      collection.val_array.forEach(function(coll_obj) { 
       if (coll_obj.value == value_obj.value) { 
        // the collection already has this object, increment it 
        coll_obj.count += value_obj.count; 
        found = true; 
        return; 
       } 
      }); 
      if (!found) { 
       // the collection doesn't have this obj yet, push it 
       collection.val_array.push(value_obj); 
      } 
     }); 
    }); 
    return collection; 
}; 

、これは作業を行い、そして100回の記録のために、それはちょうど秒ほどかかりますが、時間はそう100Mレコードがを取ると、非直線的に増加非常に長い時間。問題は、collection配列のreduce関数で劣悪なサブ集計を行っているため、collectionとmy map関数の値の両方を繰り返し処理する必要があることです。今私はちょうど(これは複数の削減が必要な場合でも)効率的にこれを行う方法を把握する必要があります。どんな提案も歓迎です!


EDIT掲載する場所が不足しているため、ここに私の解決策があります。インクリメンタル時(! dateによって並べ替えインデックスを持っている必要があります)100kのドキュメントを減らし、還元/マップ、私のコレクション全体にわたる

map = function() { 
    if (!this.fruits) return; 
    var skip_fruits = { 
     'Watermelon':1, 
     'Grapefruit':1, 
     'Tomato':1 // yes, a tomato is a fruit 
    } 
    for (var fruit in this.fruits) { 
     if (skip_fruits[fruit]) continue; 
     var obj = {}; 
     obj[this.fruits[fruit]] = 1; 
     emit(fruit, obj); 
    } 
}; 

reduce = function(key, values) { 
    var out_values = {}; 
    values.forEach(function(v) { 
     for(var k in v) { // iterate values 
      if (!out_values[k]) { 
       out_values[k] = v[k]; // init missing counter 
      } else { 
       out_values[k] += v[k]; 
      } 
     } 
    }); 
    return out_values; 
}; 

var in_coll = "fruit_repo"; 
var out_coll = "fruit_agg_so"; 
var total_docs = db[in_coll].count(); 
var page_size = 100000; 
var pages = Math.floor(total_docs/page_size); 
print('Starting incremental MR job with '+pages+' pages'); 
db[out_coll].drop(); 
for (var i=0; i<pages; i++) { 
    var skip = page_size * i; 
    print("Calculating page limits for "+skip+" - "+(skip+page_size-1)+"..."); 
    var start_date = db[in_coll].find({},{date:1}).sort({date:1}).skip(skip).limit(1)[0].date; 
    var end_date = db[in_coll].find({},{date:1}).sort({date:1}).skip(skip+page_size-1).limit(1)[0].date; 
    var mr_command = { 
     mapreduce: in_coll, 
     map: map, 
     reduce: reduce, 
     out: {reduce: out_coll}, 
     sort: {date: 1}, 
     query: { 
      date: { 
       $gte: start_date, 
       $lt: end_date 
      } 
     }, 
     limit: (page_size - 1) 
    }; 
    print("Running mapreduce for "+skip+" - "+(skip+page_size-1)); 
    db[in_coll].runCommand(mr_command); 
} 

そのファイルの繰り返し処理:


まず、私は mr.jsというファイルを作成しましたそれらを単一の出力コレクションに変換します。これは次のように使用されます: mongo db_name mr.js

その後、数時間後、私はすべての情報を持つコレクションを持っています。ほとんどの色を持っていた果物を把握、私はトップ25を印刷するのmongoシェルからこれを使用するには:

// Show number of number of possible values per key 
var keys = []; 
for (var c = db.fruit_agg_so.find(); c.hasNext();) { 
    var obj = c.next(); 
    if (!obj.value) break; 
    var len=0;for(var l in obj.value){len++;} 
    keys.push({key: obj['_id'], value: len}); 
} 
keys.sort(function(a, b){ 
    if (a.value == b.value) return 0; 
    return (a.value > b.value)? -1: 1; 
}); 
for (var i=0; i<20; i++) { 
    print(keys[i].key+':'+keys[i].value); 
} 

このアプローチについては本当にクールなところは、インクリメンタルなので、私は出力で動作することができるということですmapreduceが実行されている間のデータ。

答えて

8

実際にはval_arrayは必要ないと思われます。なぜ単純なハッシュを使用しないのですか?これを試してみてください:

map = function() { 
    if (!this.fruits) return; 
    for (var fruit in this.fruits) { 
     emit(fruit, 
      {this.fruits[fruit]: 1}); 
    } 
}; 

reduce = function(key, values) { 
    var colors = {}; 

    values.forEach(function(v) { 
    for(var k in v) { // iterate colors 
     if(!colors[k]) // init missing counter 
     colors[k] = 0 

     color[k] += v[k]; 
    } 
    }); 

    return colors; 
} 
+0

うわー、私は本当に1つは、私ではないと思っていた!これは確かに私が欲しかったのとまったく同じです。私は100,1000,100,000のレコードでそれをテストし、それは各セット(これらのサイズでは明らかに直線的)で約20k/sec実行しています。私は現在10Mのレコードを全部実行していますが、マップされたデータのバッチが大きくなるにつれて、 '' colors''オブジェクトが大きくなる必要があります。 '' secs_running ":488、msg ":" m/r:(1/3)フェーズ383999/10752083 3% "を発行します。 – SteveK

+0

Btw、私はキーが動的に生成されたので 'emit(fruit、{this.fruits [fruit]:1});'を使うことができませんでしたので、代わりにこのJSハックを使用しました: 'var obj = {}; obj [this.fruits [果物]] = 1; emit(果物、オブジェクト); '。 – SteveK

+0

私は部分的な仕事を試してみることをお勧めします。つまり、100kのバッチでドキュメントを処理してから、最後の1つのジョブでドキュメントを縮小します。これは実装するのが難しいかもしれないので、もしそれが単発であれば、私は気にしません。 :) –

0

を私はあなたにこれを教えて残念だけど、MongoDBのMapReduceのフレームワークは非常に遅く、おそらく「かなり長い間」のためにそうであり続けるであろう(私は改善があることを期待していません彼らのロードマップに)。 http://docs.mongodb.org/manual/reference/aggregation/

または上のHadoopを実行している:

単に、私の答えは、私はモンゴ-MapReduceのではなく、代わりに新しい集約フレームワークの助けを借りて、それを実装するに焦点を当てることはしないだろうということでしょう http://www.slideshare.net/spf13/mongodb-and-hadoop(素敵でシンプルなイントロ)

実装されたMapReduce機能を使用すると、MongoDBの動作が遅くなるという問題もありました。私の結論は、最も単純なタスクを実行しても、性能面では上記の2つのソリューションがあります。新しいアグリゲーションフレームワークを使用してコモディティハードウェアで> 1M docs/sec(またはそれ以上)を簡単に処理できます。