MongoDB的map/reduce操作中去除reduce部分的含义

18

我有大约 25k 个文档(原始json格式,大小为4 GB),想对它们进行一些JavaScript操作,以使最终的数据消费方(R)能够更加方便地获取数据。我希望通过为每次更改添加一个新集合来“版本控制”这些更改,但是如果没有 reduce,我无法弄清楚如何进行 map/reduce。我需要一对一文档映射-在collection_1中开始时有25356个文档,最终想以同样的数量,在collection_2中结束。

我可以用以下方法来解决:

var reducer = function(key, value_array) {
    return {key: value_array[0]}
}

然后像这样调用:

db.flat_1.mapReduce(mapper, reducer, {keeptemp: true, out: 'flat_2'})

(我的映射器只调用一次emit,第一个参数是字符串,第二个参数是最终文档。我真正想要的是这些第二个参数的集合。)

但这似乎很笨拙,而且我不知道为什么它甚至能工作,因为我的映射器中emit调用的参数与我的reducer返回的参数不等同。此外,最终我得到了这样一个文档:

{
    "_id": "0xWH4T3V3R", 
    "value": {
        "key": {
            "finally": ["here"],
            "thisIsWhatIWanted": ["Yes!"]
        }
    }
}

这似乎是不必要的。

此外,执行自己插入的光标甚至不到 mapReduce 的十分之一快。我不太了解 MongoDB,无法进行基准测试,但我猜测它大约慢了 50x。有没有一种方法可以并行地遍历光标?如果我 collection_2 中的文档与 collection_1 中的文档顺序不同,我也不介意。


它能够工作的原因是因为您的emit和reducer调用相同的。由于您使用value [0]作为reducer的输出,因此它必须完全相同,因为您没有更改它(它只是通过您的reducer)。 - null
5个回答

6

当使用map/reduce时,你总会得到

{ "value" : { <reduced data> } }

为了删除value键,您需要使用finalize函数。
这是从一个集合复制数据到另一个集合的最简单方法:
map = function() { emit(this._id, this ); }
reduce = function(key, values) { return values[0]; }
finalize = function(key, value) { db.collection_2.insert(value); }

然后当您正常运行时:
db.collection_1.mapReduce(map, reduce, { finalize: finalize });

4
最终化函数不应为任何原因访问数据库。 - bloudermilk
确实如此,但能够做到这一点仍然很有用。 - Ian Lewis
这是一个完整的性能瓶颈,违反了Map-Reduce的原则!请不要这样做。 - Diego Sevilla

3
但这似乎很尴尬,我不知道为什么它会起作用,因为我的mapper中的emit调用参数与我的reducer的返回参数并不相等。
它们是相等的。reduce函数接收一个T值数组,并应以相同的T格式返回单个值。 T的格式由map函数定义。您的reduce函数只返回values数组中的第一项,该项始终为类型T。 这就是为什么它起作用的原因 :)
您似乎正在正确的轨道上。我进行了一些实验,发现您无法从map函数中执行db.collection.save(),但您可以从reduce函数中执行此操作。 您的map函数应仅构造所需的文档格式:
function map() {
  emit(this._id, { _id: this.id, heading: this.title, body: this.content });
}

map函数会重用原始文档的ID。这应该可以防止任何重新减少步骤,因为没有值将共享相同的键。

reduce函数可以简单地返回null。但是,您还可以将值写入单独的集合

function reduce(key, values) {
  db.result.save(values[0]);

  return null;
}

现在,db.result 应该包含经过转换的文档,不会有临时集合中可能存在的任何额外的map-reduce噪声。我没有在大量数据上测试过这种方法,但这种方法应该利用了map-reduce函数的并行执行。

2
这种方法花费了523秒,并最终得到了我想要的完美集合,而我在问题中描述的hackish方法则需要319秒。很遗憾我不能只调用db.coll.mapReduce(myMapperFunc, null, {'out': 'output'})。我认为reduce能够批量保存/插入整个项目集;我认为瓶颈在于每次reduce都会调用save() - chbrown
1
@chbrown:是的,每个文档都会进行两次save()操作;一次是标准的reduce-save到临时集合,另一次是显式保存到单独的集合中。只是好奇,这种解决方案是否比使用单个游标更快? - Niels van der Rest
大家好,我们遇到了一个类似的问题,需要处理大数据集,由于数组连接和在reduce中返回大文档不起作用,我们已经采用了上述方法,将文档保存在单独的集合中,并从reduce中返回null。这个方法很有效,但是当我们在运行mapreduce时进行其他操作时,数据库会挂起。有没有更好的方法来解决这个问题? - MRK

1
当您获得mongo shell的访问权限后,它会接受一些Javascript命令,然后变得更简单:
map = function(item){
        db.result.insert(item);
}

db.collection.find().forEach(map);

0

我曾经遇到过同样的情况。我通过Mongo查询和投影来完成了这个任务。请参考Mongo Query



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接