带有索引的小型集合的 $lookup/$sort 性能不佳

Poor $lookup/$sort performance on small collections WITH indexes

我已经在 SO 上浏览了将近 10 个类似的帖子,但我仍然对我得到的结果感到困惑:在 42K 之间的单个 $lookup 聚合上对外部字段进行排序需要 5 秒以上文件收集和 19 记录收集。 Aka,总叉积为 798K。

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

话虽这么说,但我似乎无法理解为什么接下来的内容会花费这么长时间。我觉得我一定是做错了什么。

上下文:

  1. A 4 vCPU, 16 GB RAM VM 运行ning Debian 10 / MongoDB 4.4 作为单节点副本集,什么都没有 别的。完全更新 .NET MongoDB 驱动程序(我刚刚更新并重新测试)

  2. 聚合中有一个查找,与'from'集合 有 42K 个文档,'to' 集合有 19 个文档。

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

  4. 'to' 集合中的外部字段有一个索引。是的,仅针对那 19 条记录,以防万一。

  5. 其中一篇关于 $lookup 性能缓慢的帖子提到如果 $eq 没有​​在 $lookup 阶段的嵌套管道中使用,它就不会使用索引。所以我确保聚合管道使用 $eq 运算符。

这是管道:

[{ "$lookup" : 
    { "from" : "4", 
    "let" : { "key" : "" }, 
    "pipeline" : 
        [{ "$match" : 
            { "$expr" : 
                { "$eq" : ["$id", { "$substrCP" : ["$$key", 0, { "$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 秒,但是,这些阶段对于 link/lookup 是必要的到适当的子文档。在 0.5 秒内完成不需要查找的 'from' 集合排序。

我在这里做错了什么?提前致谢!

编辑:

我刚刚用更大的记录集测试了同样的事情('from' 集合中有 38K 条记录,'to' 集合中有 26K 条记录,一对一关系)。花了7分多钟才完成排序。我查看了 Compass,发现“id”上的索引确实被使用了(在 7 分钟内不断刷新并看到它上升,我目前是该数据库的唯一用户)。

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

[{ "$lookup" : 
    { "from" : "1007", 
    "let" : { "key" : "" }, 
    "pipeline" : 
        [{ "$match" : 
            { "$expr" : { "$eq" : ["$id", "$$key"] } } }], 
    "as" : "L1" } }, 
{ "$sort" : { "L1.1" : -1 } }, 
{ "$project" : { "L1" : 0 } }, 
{ "$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', 
  localField: 'uid', 
  foreignField: 'uid', 
  as: 'test' } }]).toArray()

是什么导致了 shell 与 Compass 和 .NET 驱动程序之间的差异?

对于遇到此问题的任何人 post,以下方法对我有用:使用 $lookup 运算符的 localField/foreignField 版本。

在 Compass 中监控索引时,let/pipeline 和 localField/foreignField 版本都命中了相应的索引,但在使用 let/pipeline 版本时速度慢了几个数量级。

我重组了我的查询构建逻辑以仅使用 localField/foreignField,这让世界变得不同。