我在SO上看到了很多关于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 函数,最终它们要么不起作用,要么花费指数级的时间。 在示例(水果(的上下文中,我在大约 10,000,000 个文档中有大约 1,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 函数中进行 poor-mans 子聚合,因此需要我迭代 map 函数中的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);
}
该文件遍历我的整个集合,一次增量映射/减少 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 文档/秒(甚至更多(。