4

MongoDBでの集計について、SOについて多くの質問がありますが、私の完全な解決策はまだ見つかりません。

これが私のデータの例です:

{
    "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の異なる果物と100,000の色があり、合計で約10,000,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レコードの場合、1秒ほどかかりますが、時間は非線形に増加するため、1億レコードには非常に長い時間がかかります。問題は、reduce関数で配列を使用して貧弱なサブアグリゲーションを実行しているため、map関数の値とcollection両方を反復処理する必要があることです。collectionここで、これを効率的に行う方法を理解する必要があります(複数の削減が必要な場合でも)。どんな提案でも大歓迎です!


編集それを投稿するためのより良い場所がないため、ここに私の解決策があります。
まず、次のファイルを作成しましたmr.js

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);
}

そのファイルは私のコレクション全体を反復処理しdate、一度に100k個のドキュメント(インデックスが必要なソート順)を段階的にマップ/削減し、それらを単一の出力コレクションに削減します。これは次のように使用されます:mongo db_name mr.js

それから、数時間後、私はすべての情報を含むコレクションを手に入れました。どの果物が最も色が多いかを把握するために、mongoシェルからこれを使用して上位25を印刷します。

// 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の実行中に出力データを操作できることです。

4

2 に答える 2

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;
}
于 2012-05-06T04:06:08.947 に答える
0

申し訳ありませんが、MongoDB MapReduceフレームワークは非常に低速であり、おそらく「かなりの期間」はその状態が続くでしょう(ロードマップに改善が見られるとは思いません)。

簡単に言うと、私の答えは、Mongo-MapReduceではそれを行わず、代わりに新しい集約フレームワークの助けを借りて実装することに焦点を当てることです:http: //docs.mongodb.org/manual/reference/aggregation/

またはHadoopを上で実行する:http: //www.slideshare.net/spf13/mongodb-and-hadoop(素晴らしくシンプルなイントロ)

実装されたMapReduce機能を使用すると、MongoDBが遅くなるという問題もありました。私の結論は、最も単純なタスクを実行しても、パフォーマンスに関しては上記の2つのソリューションにさえ近づかないということです。新しい集約フレームワークを使用して、コモディティハードウェアで100万ドキュメント/秒(またはそれ以上)を簡単に処理できます。

于 2012-06-05T13:23:02.760 に答える