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が実行されている間のデータ。
うわー、私は本当に1つは、私ではないと思っていた!これは確かに私が欲しかったのとまったく同じです。私は100,1000,100,000のレコードでそれをテストし、それは各セット(これらのサイズでは明らかに直線的)で約20k/sec実行しています。私は現在10Mのレコードを全部実行していますが、マップされたデータのバッチが大きくなるにつれて、 '' colors''オブジェクトが大きくなる必要があります。 '' secs_running ":488、msg ":" m/r:(1/3)フェーズ383999/10752083 3% "を発行します。 – SteveK
Btw、私はキーが動的に生成されたので 'emit(fruit、{this.fruits [fruit]:1});'を使うことができませんでしたので、代わりにこのJSハックを使用しました: 'var obj = {}; obj [this.fruits [果物]] = 1; emit(果物、オブジェクト); '。 – SteveK
私は部分的な仕事を試してみることをお勧めします。つまり、100kのバッチでドキュメントを処理してから、最後の1つのジョブでドキュメントを縮小します。これは実装するのが難しいかもしれないので、もしそれが単発であれば、私は気にしません。 :) –