带有索引的小型集合的$ lookup / $ sort性能较差

问题描述

我在SO上浏览了近10篇类似的文章,但我仍然对得到的结果感到困惑:5万秒以上用于在42K文档集合之间的单个$ lookup聚合中对异类进行排序和19条唱片收藏。又名,总叉积为798K。

不幸的是,在这里非规范化不是一个好的选择,因为'to'集合中的文档被大量共享,并且在进行更改时将需要在数据库中进行大量更新。

话虽这么说,但我似乎无法理解以下内容为何要花这么长时间。我觉得我一定做错了事。

上下文:

  1. 一个4 vcpu,16 GB RAM VM作为单个节点副本集运行Debian 10 / MongoDB 4.4 其他。完全更新的.NET MongoDB驱动程序(我刚才已更新并重新测试)

  2. 聚合中只有一个查询,其中包含“ from”集合 有42K个文档,“ to”集合有19个文档。

  3. 所有聚合,索引和集合都使用认排序规则。

  4. “ to”集合中的外部字段具有索引。是的,仅对这19条记录有所帮助。

  5. 其中一篇有关$ lookup性能较慢的文章提到,如果在$ lookup阶段的嵌套管道中未使用$ eq,则不会使用索引。因此,我确保聚合管道使用$ eq运算符。

这是管道:

[{ "$lookup" : 
    { "from" : "4","let" : { "key" : "$1" },"pipeline" : 
        [{ "$match" : 
            { "$expr" : 
                { "$eq" : ["$id",{ "$substrCP" : ["$$key",{ "$indexOfCP" : ["$$key","_"] }] }] } } },{ "$unwind" : { "path" : "$RF2" } },{ "$match" : { "$expr" : { "$eq" : ["$RF2.id","$$key"] } } },{ "$replaceRoot" : { "newRoot" : "$RF2" } }],"as" : "L1" } },{ "$sort" : { "L1.5" : 1 } },{ "$project" : { "L1" : 0 } },{ "$limit" : 100 }]

取出嵌套的$ unwind / $ match / $ replaceRoot组合可节省大约30%的运行时间,使其降至3.5秒,但是,这些阶段对于链接/查找到正确的子文档是必需的。不需要查找就可以对'from'集合进行排序。

在这里做错了什么?预先感谢!

编辑:

我刚刚用更大的记录集(“ from”集合中的38K记录,“ to”集合中的26K记录,一对一关系)测试了同一件事。花了7分钟以上的时间来完成排序。我检查了Compass后,发现实际上正在使用“ id”上的索引(在7分钟内保持刷新状态,看到它上升了,我是数据库的唯一用户)。

这是管道,比第一个管道更简单:

[{ "$lookup" : 
    { "from" : "1007","let" : { "key" : "$13" },"pipeline" : 
        [{ "$match" : 
            { "$expr" : { "$eq" : ["$id","$$key"] } } }],{ "$sort" : { "L1.1" : -1 } },{ "$limit" : 100 }]

根据上述信息,听起来7分钟合理吗?

编辑2:

shell代码创建具有两个字段(名称:字符串,uid:整数)的两个40k记录集合(prod和prod2):

var randomName = function() { 
    return (Math.random()+1).toString(36).substring(2); 
}

for (var i = 1; i <= 40000; ++i) { 
    db.test.insert({ 
        name: randomName(),uid: i }); 
}

我在prod2的'uid'字段上创建了一个索引,将Compass的示例文档限制增加到50k,然后进行了以下查找,这花费了整整两分钟的时间:

{ from: 'prod2',localField: 'uid',foreignField: 'uid',as: 'test' }

编辑3:

我还直接从Shell运行了聚合管道,并在几秒钟而不是两分钟内获得了结果:

db.test1.aggregate([{ $lookup:
{ from: 'test2',as: 'test' } }]).toArray()

是什么原因导致Shell与Compass和.NET驱动程序之间出现差异?

解决方法

对于任何绊倒这篇文章的人,以下对我有用:使用$ lookup运算符的localField / foreignField版本。

在Compass中监视索引时,let / pipeline版本和localField / foreignField版本都达到了适当的索引,但是使用let / pipeline版本时,速度要慢几个数量级。

我将查询构建逻辑重组为仅使用localField / foreignField,这带来了很大的改变。