mongodb - 通过 mod(ObjectId) 选择文档的一部分

标签 mongodb mongodb-query aggregation-framework

我有一个很大的集合,正在其上运行聚合管道。我有 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 万个文档,并生成大约 20 万个文档。查询运行几分钟,然后失败并显示“聚合结果超出最大文档大小 (16MB)”。

最佳答案

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

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

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

在下一次调用时,您将采用“最后一个” _id聚合的值(value)并更改您的匹配管道,如下所示:

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是否存在,因为虽然第一个大部分会排除结果,但需要排除“组合键”。这本质上就是为什么你不能只做 $gt 在第一$match因为组合中共享第一个元素的第二个元素仍然可能有更大的值。

你还是$sort$limit每次迭代并继续,直到返回结果的数量小于您设置的限制。

还有 $skip 聚合管道的运算符,但这不是很高效,因为您将增加每 1000 个文档的“跳过”次数,直到处理 200,000 个结果。所以非常慢。

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

这里的主要问题是 _id 组合,它本质上是结果。仅仅找到两者组合范围的“分割”将变得非常困难。因此,这里的妥协是每次迭代都会变得更快的查询。

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

应该尽快考虑迁移到 MongoDB 2.6。

关于mongodb - 通过 mod(ObjectId) 选择文档的一部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25193805/

相关文章:

mongodb - 更新/删除 MongoDB 集合中的子文档

mongodb - 在 mongodb 中聚合每小时、每周、每月、每年的数据

java - 我应该如何使用 Spring Data MongoDB 计算由另一个字段分组的字段总和

c - Mongo C 驱动程序通过 _id 访问文档

PHP/MongoDB : update a value in an array

python - 使用allowDiskUse时语法无效:True through PyMongo

node.js - 如何使用 mongoose/mongodb 在单个查询中应用 $match、$group 和 $sum

mongodb - 从 MongoDB 中删除重复项

mongodb - 为多个日期范围聚合 $group

MongoDB 聚合 : Group on common field of two arrays