簡體   English   中英

通過map reduce的MongoDB唯一值聚合

[英]MongoDB unique value aggregation via map reduce

我在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種不同的水果和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條記錄,它只需要一秒鍾左右,但時間非線性增加,因此100M記錄需要長時間。 問題是我在reduce函數中使用collection數組進行窮人子聚合,因此需要我迭代collection和map函數中的值。 現在我只需要弄清楚如何有效地做到這一點(即使它需要多次減少)。 歡迎任何建議!


編輯由於缺少更好的發布地點,這是我的解決方案。
首先,我創建了一個名為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); } 

該文件遍歷我的整個集合,逐步映射/減少100k文檔(按date排序,必須有索引!),並將它們縮減為單個輸出集合。 它的使用方式如下: mongo db_name mr.js

然后,幾個小時后,我收集了所有信息。 為了弄清楚哪些水果顏色最多,我使用mongo shell來打印前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運行時處理輸出數據。

看來你真的不需要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;
}

很抱歉告訴你這一點,但是MongoDB MapReduce框架速度非常慢,並且可能會在“相當長一段時間內”繼續如此(我不希望他們的路線圖有所改進)。

簡單地說,我的回答是我不會用Mongo-MapReduce做到這一點,而是專注於在新聚合框架的幫助下實現它: http//docs.mongodb.org/manual/reference/aggregation/

或者在頂部運行Hadoop: http//www.slideshare.net/spf13/mongodb-and-hadoop (簡潔易懂的介紹)

我在使用實現的MapReduce功能時也遇到了MongoDB問題,我的結論是,即使在完成最簡單的任務時,在性能方面也沒有接近上述兩種解決方案。 您可以使用新的聚合框架在商用硬件上輕松處理> 1M docs / sec(甚至更多)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM