【问题标题】:MongoDB MapReduce strange resultsMongoDB MapReduce 奇怪的结果
【发布时间】:2014-07-18 08:33:21
【问题描述】:

当我对包含少量文档的 MongoDB 集合执行 Mapreduce 操作时,一切正常。

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

地图功能:

function() { emit(this.featureType, this._id); }

减少功能:

function(key, values) { return { count: values.length, ids: values };

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

{
"_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", .......}

但是我得到了这个奇怪的文档结构:

{
"_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".....}

如果这是预期的行为,或者我做错了什么,有人可以解释一下吗?

提前致谢!

【问题讨论】:

  • 文档的数量似乎是您的问题。 140,000 可以转储到看似只有几个(或您的示例 1 )数组中。为什么需要这样做?有趣的是,它确实适用于聚合。
  • 我正在将输出保存到一个新的集合中,结果文档的大小不大于16mb,据我了解,系统应该能够正确管理它。
  • 文档中包含这样做的原因。响应解释了这一点以及如何使用各种方法进行纠正。

标签: mongodb mapreduce aggregation-framework


【解决方案1】:

这里的情况不常见,鉴于正在生成的大型数组,我不确定这是否是您真正想要的。但是documentation 中的一点在 mapReduce 工作原理的假设中被遗漏了。

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

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

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

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

从本质上讲,这意味着您的“映射器”和“减速器”都必须承担更多的复杂性才能产生您想要的结果。本质上要确保“mapper”的输出以与“reducer”中相同的形式发送,reduce 进程本身也注意到了这一点。

所以首先修改映射器:

function () { emit(this.type, { count: 1, ids: [this._id] }); }

现在与最终输出形式一致。考虑到你现在知道会被多次调用的 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 };

}

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

  • 减少一大块结果#chunk1
  • 减少另一块结果#chunk2
  • 在减少的块 #chunk1 和 #chunk2 上组合 reduce

这似乎不是很明显,但这种行为是设计使然,reducer 以这种方式被多次调用以处理大量发出的数据,因此它逐渐“聚合”而不是一大步。


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

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

所以它不会中断,实际上会按预期返回,由于操作确实非常简单,因此复杂性大大降低。

但是我已经说过,考虑到庞大的规模,您在这里返回“_id”值数组的目的似乎不清楚。因此,如果您真正想要的只是“featureType”的计数,那么您将使用基本相同的方法,而不是试图强制 mapReduce 找到一个非常大的数组的长度:

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

尽管如此,无论是哪种形式,结果都是正确的,并且运行的时间只是构建的 mapReduce 操作所花费的时间的一小部分。

【讨论】:

  • 感谢您的详细解答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多