即使使用 $match

问题描述

tl;dr:我在一个集合中有 400 万个文档,我使用 $match 过滤到 6000,然后使用 $project 执行操作。 $project 需要 60 秒!如果我在仅包含 6000 个文档的模拟数据库上运行相同的 $project 操作,则需要 0.5 秒。为什么会出现这种不匹配以及如何解决

UPDATE:花费 60 秒的不是 $project 步骤,而是我在它之后放置的任何步骤。例如,另一个 $match 过滤 $project输出以获得高于 X 的点积。

我正在为 Python 使用 pymongo 库,并且我在 Windows 上安装了 MongoDb 版本 v4.4.3。 我在一个集合中有 400 万个文档。每个文件都是这样的:

{
_id: ObjectId("...")
document_id: 0,vector: Array
hash_value: 123
}

其中 vector一个包含 300 个双精度值的数组,范围从 -1 到 1。我在 hash_value 上有一个索引。

给定一个在 (-1,1) 范围内也有 300 个元素的输入数组,我想计算输入数组与集合中具有给定 hash_value 的每个数组之间的点积。
我使用聚合管道(请参阅问题末尾的完整管道)

  • $match:过滤以仅获取与所需 hash_value
  • 匹配的文档
  • $project:将点积应用于 $match 返回的元素。
  • $match:仅过滤 $project 输出的高于 X 的点积。

$match 步骤从 400 万个文档过滤到大约 6000 个文档,$project 步骤非常快,但我之后包含的任何步骤(例如第二个 $match`` **takes 1 minute** to run! Even if I just return the results from $project` `` 并在 Python 中迭代它们需要 1 分钟。

为了测试,我还创建了一个只有 6000 个文档和一个 hash_value 的模拟数据库。如果我在这个包含 6000 个文档的模拟数据库上运行完全相同的 $match,$project,$match 管道(这里 $match 将所有 6000 个文档传递到下一步,因为它们都匹配输入hash_value),只需 0.5 秒

似乎我可以通过过滤到可接受的大小来计算快速点积,但是我无法以任何方式使用结果,因为它们加载速度太慢。 >

我的问题是:

  1. 为什么会这样? (如果可能的话,我想解释一下幕后发生的事情)
  2. 有什么办法可以防止这种情况发生吗?
  3. 拆分不同集合中的文档会有帮助吗?
  4. 如果您认为使用 MongoDB 无法实现良好的性能,那么您知道我可以快速对数百万文档执行此类操作的其他数据库吗?
  5. 我的数据结构有误吗?为了计算查询和文档之间的点积,是否有更好的方法来构造我的数据?

以下是管道:

{
    "$match": {"$expr": {"$in": ["$hash_value",[123]]}}
},{"$project": {
    "_id":0,"document_id": 1,"dotProduct": {
        "$let": {"vars": {"queryVector": [0.2,-0.12,-0.9,....,0.14,0.56]},"in":{
        "$reduce": {
            "input": { "$range": [ 0,{ "$size": "$vector" }] },"initialValue": 0,"in": { "$add": [ "$$value",{ "$multiply": [ { "$arrayElemAt": [ "$vector","$$this" ] },{ "$arrayElemAt": [ "$$queryVector","$$this" ] } ] } ] }
                    }
                }
            }
        }            
    }
},{   
    "$match":{"dotProduct":{"$gt":0.5}}
}

注意事项:

  1. 我使用 $in 是因为实际上可能有多个输入 hash_value
  2. 我已验证第一个 $match 步骤实际上已正确过滤掉
  3. 我已经看到了 this 个问题。我遵循了答案中的一些提示(使用 allowdiskUse = True,索引我用于 $match 步骤的字段),但其他评论似乎都与我的案例无关。
  4. 如果我只保留第一个 $match获取结果并在 Python 中迭代它们也需要大约 1 分钟。

这里是分析器日志:


                "allowdiskUse" : true,"cursor" : {

                },"$readPreference" : {
                        "mode" : "secondaryPreferred"
                },"$db" : "similarity"
        },"keysexamined" : 0,"docsexamined" : 4000000,"cursorExhausted" : true,"numYield" : 4789,"nreturned" : 1,"queryHash" : "9A0FCEC0","planCacheKey" : "9A0FCEC0","locks" : {
                "ReplicationStateTransition" : {
                        "acquireCount" : {
                                "w" : NumberLong(4792)
                        }
                },"Global" : {
                        "acquireCount" : {
                                "r" : NumberLong(4792)
                        }
                },"Database" : {
                        "acquireCount" : {
                                "r" : NumberLong(4791)
                        }
                },"Collection" : {
                        "acquireCount" : {
                                "r" : NumberLong(4791)
                        }
                },"Mutex" : {
                        "acquireCount" : {
                                "r" : NumberLong(2)
                        }
                }
        },"flowControl" : {

        },"storage" : {
                "data" : {
                        "bytesRead" : NumberLong("15295638272"),"timeReadingMicros" : NumberLong(57621295)
                }
        },"responseLength" : 176,"protocol" : "op_query","millis" : 63990,"planSummary" : "COLLSCAN","ts" : ISODate("2021-03-26T10:06:56.185Z"),...
}

预先感谢您的慷慨帮助。

解决方法

我找到了解决方案: 第一个 $match 步骤使用了比 $in 更昂贵的 $eq。使用 $eq 将响应时间减少到 1.5 - 2.5 秒。我无法弄清楚的原因是懒惰的执行使它“看起来”像 $match 速度很快。换句话说,$match > $project 似乎运行得很快,直到我尝试从游标中获取结果。 MongoDB 实际上不会运行管道,直到您从游标中获得结果,这就是为什么很难确定管道中耗时最长的步骤的原因。

新的管道变成

{
    "$match": { "hash_value" : 123 }
},{"$project": {
    "_id":0,"document_id": 1,"dotProduct": {
        "$let": {"vars": {"queryVector": [0.2,-0.12,-0.9,....,0.14,0.56]},"in":{
        "$reduce": {
            "input": { "$range": [ 0,{ "$size": "$vector" }] },"initialValue": 0,"in": { "$add": [ "$$value",{ "$multiply": [ { "$arrayElemAt": [ "$vector","$$this" ] },{ "$arrayElemAt": [ "$$queryVector","$$this" ] } ] } ] }
                    }
                }
            }
        }            
    }
},{   
    "$match":{"dotProduct":{"$gt":0.5}}
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...