MongoDB MapReduce结果奇怪

use*_*011 3 mapreduce mongodb aggregation-framework

当我使用少量文档对MongoDB集合执行Mapreduce操作时,一切正常.

但是,当我使用大约140,000个文档的集合运行它时,我得到一些奇怪的结果:

地图功能:

function() { emit(this.featureType, this._id); }
Run Code Online (Sandbox Code Playgroud)

减少功能:

function(key, values) { return { count: values.length, ids: values };
Run Code Online (Sandbox Code Playgroud)

因此,我希望(对于每个映射键):

{
"_id": "FEATURE_TYPE_A",
"value": { "count": 140000,
           "ids": [ "9b2066c0-811b-47e3-ad4d-e8fb6a8a14e7",
                    "db364b3f-045f-4cb8-a52e-2267df40066c",
                    "d2152826-6777-4cc0-b701-3028a5ea4395",
                    "7ba366ae-264a-412e-b653-ce2fb7c10b52",
                    "513e37b8-94d4-4eb9-b414-6e45f6e39bb5", .......}
Run Code Online (Sandbox Code Playgroud)

但相反,我得到了这个奇怪的文档结构:

{
"_id": "FEATURE_TYPE_A",
"value": {
    "count": 706,
    "ids": [
        {
            "count": 101,
            "ids": [
                {
                    "count": 100,
                    "ids": [
                        "9b2066c0-811b-47e3-ad4d-e8fb6a8a14e7",
                        "db364b3f-045f-4cb8-a52e-2267df40066c",
                        "d2152826-6777-4cc0-b701-3028a5ea4395",
                        "7ba366ae-264a-412e-b653-ce2fb7c10b52",
                        "513e37b8-94d4-4eb9-b414-6e45f6e39bb5".....}
Run Code Online (Sandbox Code Playgroud)

有人可以解释我这是预期的行为,还是我做错了什么?

提前致谢!

Nei*_*unn 7

这里的情况是不常见的,我不确定这是否是你真正想要的,因为生成了大型数组.但是在文档中有一点在mapReduce如何工作的假设中被忽略了.

  • MongoDB可以为同一个密钥多次调用reduce函数.在这种情况下,该键的reduce函数的先前输出将成为该键的下一个reduce函数调用的输入值之一.

这里基本上说的是你当前的操作只是期望一次调用"reduce"函数,但事实并非如此.输入实际上将被"分解"并在此处作为可管理的大小传递.多次调用"减少"现在使另一点非常重要.

因为可以为同一个键多次调用reduce函数,所以需要满足以下属性:

  • 返回对象的类型必须 map函数发出的值的类型相同,以确保以下操作成立:

从本质上讲,这意味着您的"映射器"和"缩减器"必须具有更高的复杂性才能产生您想要的结果.基本上确保"映射器"的输出以与它在"reducer"中出现的相同的形式发送,并且reduce过程本身也注意到这一点.

所以首先修改了映射器:

function () { emit(this.type, { count: 1, ids: [this._id] }); }
Run Code Online (Sandbox Code Playgroud)

现在哪个与最终输出表格一致.在考虑您现在知道的多次调用的reducer时,这很重要:

function (key, values) {

  var ids = [];
  var count = 0;

  values.forEach(function(value)  {
    count += value.count;
    value.ids.forEach(function(id) {
      ids.push( id );
    });
  });

  return { count: count, ids: ids };

}
Run Code Online (Sandbox Code Playgroud)

这意味着reduce函数的每次调用都需要与输出相同的输入,即count字段和id数组.这基本上可以达到最终结果

  • 减少一大块结果#chunk1
  • 减少另一块结果#chunk2
  • 结合减少的块,#chunk1和#chunk2的减少

这似乎并不是很明显,但行为是设计的,其中reducer以这种方式被多次调用以处理大量的发射数据,因此它逐渐"聚合"而不是一步到位.


聚合框架使得这更加简单明了,从MongoDB 2.6及以上版本甚至可以将结果输出到集合,因此如果您有多个结果且组合输出大于16MB,那么这不会是一个问题.

db.collection.aggregate([
    { "$group": {
        "_id": "$featureType",
        "count": { "$sum": 1 },
        "ids": { "$push": "$_id" }
    }},
    { "$out": "ouputCollection" }
])
Run Code Online (Sandbox Code Playgroud)

所以这不会破坏并且实际上将按预期返回,由于操作确实非常简单,复杂性大大降低.

但是我已经说过,你在这里返回"_id"值数组的目的似乎不清楚你的意图,因为它的大小.因此,如果您真正想要的只是"featureType"的计数,那么您将使用基本相同的方法,而不是试图强制mapReduce来查找非常大的数组的长度:

db.collection.aggregate([
    { "$group": {
        "_id": "$featureType",
        "count": { "$sum": 1 },
    }}
])
Run Code Online (Sandbox Code Playgroud)

但是,在任何一种形式中,结果都是正确的,并且运行的时间只是构造的mapReduce操作所花费的时间的一小部分.