通过mod(ObjectId)选择一小部分文档

时间:2014-08-07 23:21:10

标签: 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)"。

1 个答案:

答案 0 :(得分:1)

您的结果太大,所以最好的办法是在管道的末尾实施$limit

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

由于汇总结果,因此汇总结果$sort并不保证按顺序排列,并且可能按发现顺序排列。您需要结果才能完成下一步的操作。

在下一次调用中,您将使用" last"来自聚合的_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就在那里,因为第一个{@ 1}}主要排除了结果"组合键"需要被排除在外。这就是为什么你不能在第一个$match中进行$gt的原因,因为组合中第二个元素的共享第一个元素的值仍然会更大。

每次迭代时仍然$sort$limit并继续运行,直到返回的结果数小于您设置的限制为止。

聚合管道也有$skip运算符,但这不是很有效,因为你会增加" skip"每1000份文件,直至处理您的200,000个结果。非常慢。

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

这里的主要问题是_id组合本质上就是结果。这样就很难找到一个"分裂"两者的组合范围。因此,这里的妥协是每次迭代都会更快的查询。

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

应尽快考虑迁移到MongoDB 2.6。