MongoDB MapReduce,与“相同的方法”不同的结果,我缺少什么?

时间:2013-05-29 08:30:26

标签: mongodb mapreduce

我知道我在MongoDB中遗漏了一些MapReduce。我正在尝试构建一个标记频率集合,即使看起来mapreduce函数“相同”,我也会得到不同的结果。

示例文档(忘记值100,45 ......我没有使用它们):

{
    ...
    tags: [['Rock', 100], ['Indie Pop', 45], ...]
}

发出标量值1

var map = function () {
    if (this.tags) {
        this.tags.forEach(function (tag) {
            emit(tag[0], 1); // Emit just 1
        });
    }
};

var reduce = function (key, vals) { // Vals should be [1, ...]
    return vals.length; // Count the length of the array
};

db.tracks.mapReduce(map, reduce, { out: 'mapreduce_out' });
db.mapreduce_out.find().sort({ value: -1 }).limit(3);

输出是:

{ "_id" : "rubyrigby1", "value" : 9 }
{ "_id" : "Dom", "value" : 7 }
{ "_id" : "Feel Better", "value" : 7 }

发出一个对象{ count: 1 }

var map = function () {
    if (this.tags) {
         this.tags.forEach(function (tag) {
            emit(tag[0], { count: 1 }); // Emit an object
         });
    }
};

var reduce = function (key, vals) { // vals should be [{ count: 1 }, ...]
    var count = 0;

    vals.forEach(function (val) {
        count += val.count; // Accumul
    });

    return { count: count };
};

db.tracks.mapReduce(map, reduce, { out: 'mapreduce_out' });
db.mapreduce_out.find().sort({ 'value.count': -1 }).limit(3);

结果不同,似乎是“正确的”:

{ "_id" : "rock", "value" : { "count" : 9472 } }
{ "_id" : "pop", "value" : { "count" : 7103 } }
{ "_id" : "electronic", "value" : { "count" : 5727 } }

第一种方法出了什么问题?

1 个答案:

答案 0 :(得分:4)

考虑一千个文件的集合,所有文件都带有'tagname'标签:

for (var i = 0; i < 1000; i++) {
    db.collection.insert({tags: [['tagname']]});
}

如果我写了一个合适的mapReduce,我应该得到输出{"_id": "tagname", "count": 1000}。但是如果我使用你的地图并减少功能,我将得到101而不是1000。

原因是,MongoDB使用中间结果重复调用reduce函数,以避免在内存中保留过多的结果。您可以通过在reduce:

中放置一个print语句来实际看到这一点
var reduce = function (key, vals) {
    print(vals);
    return vals.length; // Count the length of the array
};

打印输出显示在服务器日志中。使用前100个1调用reduce函数,它返回100.到目前为止一直很好。然后MongoDB再次使用第一个reduce的输出加上下一个100的1来调用它:

reduce([100, 1, 1, ..., 1]) // 100 plus 100 more 1's

所以现在它返回101,因为这是数组的长度。但显然它应该返回200,数组的总和。因此,要获得正确的结果,请更改reduce函数:

reduce = function (key, vals) {
    var sum = 0;
    vals.forEach(function(val) { sum += val; });
    return sum;
}