【问题标题】:Selecting a fraction of documents by mod(ObjectId)通过 mod(ObjectId) 选择一部分文档
【发布时间】:2014-10-01 08:40:13
【问题描述】:

我有一个正在运行聚合管道的大型集合。我有 MongoDB 2.4。我遇到了 16 MB 的限制。我知道我可以通过升级到 2.6 来解决这个问题,但这不是我的选择。

我可以低于 16 MB 限制的另一种方法是将聚合分解为几部分,然后将部分结果合并到我的应用程序代码中。我需要拆分的字段是 ObjectId。本质上,我想要的是让我的 $match 阶段使用类似的东西:

my_objid_field: {$mod: [10, n]}

我将针对不同的 n 值运行 10 次查询。但是,我不知道该怎么表达。


典型的文档如下所示:

{
    "_id" : ObjectId("514cf080358a7c3fd4113f84"),
    "a" : 1,
    "c" : "US",
    "d" : ISODate("2013-03-23T00:00:00Z"),
    "st" : ObjectId("4fcfa494c212e76b890004a2"),
    "si" : 0,
    "so" : ObjectId("4e9e58e62b28686b47e71cdf"),
    "t" : ISODate("2013-03-23T00:00:00.779Z"),
    "u" : ObjectId("4fe9845a8596aa3d990014cf"),
    "se" : "dYJgW8w/kcCIJK08"
}

来自 db.currentOp() 的管道是:

        "pipeline" : [
            {
                "$match" : {
                    "$or" : [
                        {
                            "du" : {
                                "$gt" : 25
                            }
                        },
                        {
                            "du" : {
                                "$exists" : false
                            }
                        }
                    ],
                    "bu" : {
                        "$exists" : false
                    },
                    "t" : {
                        "$gte" : ISODate("2013-03-23T00:00:00Z"),
                        "$lt" : ISODate("2013-03-24T00:00:00Z")
                    }
                }
            },
            {
                "$group" : {
                    "c" : {
                        "$sum" : 1
                    },
                    "_id" : {
                        "t" : "$st",
                        "o" : "$so"
                    }
                }
            }
        ]

该查询匹配大约 2000 万个文档,并产生大约 200000 个文档。查询运行了几分钟,然后失败并显示“聚合结果超出最大文档大小 (16MB)”。

【问题讨论】:

  • 你在哪里突破限制?在管道中还是在结果中?您可以通过在问题中包含聚合操作来更好地表达您的问题,然后清楚要分解什么。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

您的结果太大,因此最好的办法可能是在管道末尾实现$limit

db.collection.aggregate([
    // same $match
    // same $group
    { "$sort": { "_id": 1 } },
    { "$limit": 1000 }      // or whatever you can go to without breaking
])

问题是$sort,因为您汇总的结果不能保证按顺序排列,并且可能按发现顺序排列。您需要结果才能执行下一步操作。

在下一次调用中,您从聚合中获取“最后一个”_id 值并按如下方式更改匹配管道:

db.collection.aggregate([
    { "$match" : {
        "st": { "$gte": ObjectId("4fcfa494c212e76b890004a2") }, // part of last result
        "$or" : [
            { "du" : { "$gt" : 25 } },
            { "du" : { "$exists" : false } }
        ],
        "bu" : { "$exists" : false },
        "t" : {
            "$gte" : ISODate("2013-03-23T00:00:00Z"),
            "$lt" : ISODate("2013-03-24T00:00:00Z")
        }
    }},
    { "$group": {
        "_id": { "t" : "$st", "o" : "$so" },
        "c" : { "$sum" : 1 },
    }},
    { "$match": { 
        "_id": {              // Both elements of the last seen _id
            "$ne": {
                "t": ObjectId("4fcfa494c212e76b890004a2"),
                "o": ObjectId("4e9e58e62b28686b47e71cdf")
            }
        }
    }},
    { "$sort": { "_id": 1 } },
    { "$limit": 1000 }
])         

由于您在聚合结果中使用“st”并且提供的值是最后一组结果中看到的最后一个值,因此所有小于该值的值都将被排除。

最后的$match 在那里,因为虽然第一个大部分会排除结果,但需要排除“组合键”。这就是为什么您不能只在第一个 $match 中执行 $gt 的原因,因为在共享第一个元素的组合中,第二个元素的值仍然可能更大。

您仍然在每次迭代中 $sort$limit 并继续前进,直到返回的结果数量小于您设置的限制。

还有用于聚合管道的 $skip 运算符,但这不是很高效,因为您会增加每 1000 个文档的“跳过”,直到处理 200,000 个结果。太慢了。

最好的方法是排除已经看到的值,然后一直切断管道结果。

这里的主要问题是 _id 组合,它本质上是结果。很难找到两者组合范围的“拆分”。所以这里的折衷方案是每次迭代都会变得更快的查询。

最后,出于性能原因,现在将“st”字段包含在复合索引中变得很重要,因为它可以在$match 中使用,因为它是最有效的形式。

应尽快考虑迁移到 MongoDB 2.6。

【讨论】:

  • 谢谢。这是一种有趣的方法,听起来应该可行。不幸的是,迁移到 2.6 不是一个选择。我们已经尝试了 2.6;它破坏了我们现有的一些应用程序代码(我现在不记得破坏的细节)。
  • @RoySmith 以上都不依赖于 MongoDB 2.6。最后提出建议是因为您确实应该这样做。除了自 2.6.3 以来通常已解决的 geoSpatial 查询的一些可能问题外,我个人不知道任何“破坏代码”的事情。 2.6.4 即将发布。在以前的版本中有一些实际上是“错误”的小问题,其中一些查询表单不应该工作但可以工作。但是这里没有任何低于 2.2 的特定版本不起作用。
猜你喜欢
  • 1970-01-01
  • 2013-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-22
  • 2013-02-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多