Mongodb聚合管道很慢

时间:2014-06-02 13:39:15

标签: mongodb aggregation-framework

我有一个30mb大小的数据库,它有300个文件存储在一个集合中,它们的大小从1mb到10kb不等。我正在使用 2.6 附带的新聚合框架,我没有任何索引。

我有一个汇总管道如下:

1. $match > first query match
2. $project > exclude some fields for efficiency
3. $unwind > unwind one of the arrays
4. $unwind > unwind second array
5. $project > projection to find matching fields among two arrays with $eq
6. $match > same:true
7. $group > put the unwinded arrays together
8. $limit(50)

以上此管道需要 30 秒。 如果我删除$ limit,则需要很长时间。我的问题是:

数据库大小仅 30MB ,管道根本不复杂。为什么需要这么长时间?有什么想法?


修改

我的架构如下:

{
username: string (max 20 chars
userid : string max 20 chars
userage : string max 20 chars
userobj1: array of objects, length: ~300-500 

// example of userobj1:
    [
       { 
          innerobj1: array of objects, length: ~30-50
          innerobj2: array of objects, length: ~100-200
          userinfo1: string max 20 chars
          userinfo2: string max 20 chars
          userinfo3: string max 20 chars
          userinfo4: string max 20 chars
       } ...
    ]

userobj2: same as userobj1
userobj3: same as userobj1
userobj4: same as userobj1
}

上面的这个文件有多达3-4级的内部对象。抱歉,我无法提供示例,但别名应该足够了。示例查询如下:

1. $match: 
    $and : [
             {userobj1: $elemMatch: {userinfo1:a}}, 
             {userobj1: $elemMatch: {userinfo4:b}}
           ]
2. $project {username:1, userid:1, userobj1:1, userobj2:1}
3. $unwind userobj1
4. $unwind userobj2
5. $project 
        {
          username:1, 
          userid:1, 
          userobj1:1, 
          userobj2:1,
          userobj3:1, 
          userobj4:1, 
          "same" : {
              $eq: [ userobj3.userinfo4, userobj4.userinfo4 ]
           }
         }
6. $match {same:true}
7. $group all arrays back
8. limit 50.

3 个答案:

答案 0 :(得分:2)

这里有些东西我不知道你在这里想要做什么。所以请关注我看到的可能的实际问题和答案。

考虑到这种简化的数据集:

{
    "obj1": [
        { "a": "a", "b": "b" },
        { "a": "a", "b": "c" }
    ],
    "obj2": [
        { "a": "c", "b": "b" },
        { "a": "c", "b": "c" }
    ]
},
{
    "obj1": [
        { "a": "a", "b": "b" }
    ],
    "obj2": [
        { "a": "a", "b": "c" }
    ]
}

问: "您是不是只想将文档与{ "a": "a", "b": b" }匹配在" obj1"还有{ "b": "b" }" object2"?"

如果是这种情况,那么这只是一个.find()的简单查询:

db.collection.find({
    "obj1": {
        "$elemMatch": { "a": "a", "b": "b" }
    },
    "obj2.b": "b"
})

只匹配满足条件的其中一个文档,在这种情况下仅匹配一个

{
    "obj1": [
        { "a": "a", "b": "b" },
        { "a": "a", "b": "c" }
    ],
    "obj2": [
        { "a": "c", "b": "b" },
        { "a": "c", "b": "c" }
    ]
}

问: "您是否可能尝试在条件为true的数组中查找位置?"

如果是这样,MongoDB 2.6可以使用一些操作符来帮助您而不使用$unwind

db.objects.aggregate([
    { "$match": {
        "obj1": {
            "$elemMatch": { "a": "a", "b": "b" }
        },
        "obj2.b": "b"
    }},
    { "$project": {
        "obj1": 1,
        "obj2": 1,
        "match1": {
            "$map": {
                "input": "$obj1",
                "as": "el",
                "in": {
                    "$and": [
                        { "$eq": [ "$$el.a", "a" ] },
                        { "$eq": [ "$$el.b", "b" ] }
                    ]
                }
            }
        },
        "match2": {
            "$map": {
                "input": "$obj2",
                "as": "el",
                "in": {
                    "$eq": [ "$$el.b", "b" ]
                }
            }
        }
    }}
])

给你:

{
    "obj1": [
        { "a": "a", "b": "b" },
        { "a": "a", "b": "c" }
    ],
    "obj2": [
        { "a": "c", "b": "b" },
        { "a": "c", "b": "c" }
    ],
    "match1" : [
            true,
            false
    ],
    "match2" : [
            true,
            false
    ]
}

问: "或者您可能正在尝试"过滤"只有符合这些条件的匹配数组元素?"

您可以使用MongoDB 2.6中的更多集合运算符执行此操作,而无需使用$unwind

db.objects.aggregate([
    { "$match": {
        "obj1": {
            "$elemMatch": { "a": "a", "b": "b" }
        },
        "obj2.b": "b"
    }},
    { "$project": {
        "obj1": {
            "$setDifference": [
                { "$map": {
                    "input": "$obj1",
                    "as": "el",
                    "in": {
                         "$cond": [
                           { "$and": [
                                { "$eq": [ "$$el.a", "a" ] },
                                { "$eq": [ "$$el.b", "b" ] }
                            ]},
                            "$$el",
                            false
                        ]
                    }
                }},
                [false]
            ]
        },
        "obj2": {
            "$setDifference": [
                { "$map": {
                    "input": "$obj2",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.b", "b" ] },
                            "$$el",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])

结果:

{
    "obj1": [
        { "a": "a", "b": "b" },
    ],
    "obj2": [
        { "a": "c", "b": "b" },
    ]
}

最后一个条目是最可爱的,它结合了$cond$map$setDifference来对数组中的对象进行一些复杂的过滤,以便仅过滤条件的匹配。您以前必须$unwind$match才能获得这些结果。

所以$unwind$group都不需要实际获得任何这些结果,而这些实际上是在杀死你。你的大人物也会通过"在'#34; unwound"带有$eq的数组建议尝试获得上述其中一个的最终结果,但是你实现它的方式会非常昂贵。

还尝试在其中一个数组中包含一个索引,以使该元素匹配,从而尽可能减少您的工作结果。在所有情况下,即使你不能使用复合物,它也可以改善一些东西。索引由于那里的限制。

无论如何,希望至少有一些符合你意图的东西,或者至少接近你想要的东西。


由于您的评论采用了这种方式,因此匹配" obj1.a"的值到" obj2.b"没有过滤与显示的一般情况没有太大的不同。

db.objects.aggregate([
    { "$project": {
        "match": {
            "$size": {
                "$setIntersection": [
                    { "$map": {
                        "input": "$obj1",
                        "as": "el",
                        "in": { "$concat": ["$$el.a",""] }
                    }},
                    { "$map": {
                        "input": "$obj2",
                        "as": "el",
                        "in": { "$concat": ["$$el.b",""] }
                    }}
                ]
            }
        }
    }},
    { "$match": { "$gte": 1 } }
])

所有操作都是在不使用$unwind的情况下完成的。

答案 1 :(得分:1)

我的猜测是需要很长时间,因为没有索引,因此每次需要记录时都会进行完整的集合扫描。

尝试在userinfo1:a上添加索引,我认为您会看到良好的性能提升。我还建议您从AND阶段删除match语法并将其重写为列表。

我认为对你和问题提供聚合解释的输出真的很有帮助。在mongo 2.6中,您可以拥有explain in aggregation pipeline

db.collection.aggregate( [ ... stages ...], { explain:true } )

答案 2 :(得分:1)

我知道这是一个老问题,但看起来似乎没有达到一个简单的答案,它涉及使用2.6中提供的表达式,因此它也可以使用。您无需执行任何$unwind或复杂$map ping操作,只需要在要查找匹配项的两个阵列上执行$setIntersection

使用来自很长答案的示例数据:

db.foo.aggregate(
   {$match:{"obj1.a":"a"}},
   {$project:{keep:{$setIntersection:["$obj1.b","$obj2.b"]},obj1:1,obj2:1}},
   {$match:{keep:{$ne:[]}}})
{ "_id" : ObjectId("588a8206c01d80beca3a8e45"), "obj1" : [ { "a" : "a", "b" : "b" }, { "a" : "a", "b" : "c" } ], "obj2" : [ { "a" : "c", "b" : "b" }, { "a" : "c", "b" : "c" } ], "keep" : [ "b", "c" ] }

只保留两个文档中的一个,即在obj1和obj2数组中都有两个“b”值的文档。

在原始的“语法”中,$project阶段将是

 same: {$setIntersection: [ '$userobj3.userinfo4', '$userobj4.userinfo4' ]}