mongodb - 即使在使用 $match 过滤后,mongodb (pymongo) 聚合也很慢
问题描述
tl; dr:我在一个集合中有 400 万个文档,我过滤到 6000 个,$match
然后使用$project
. 需要$project
60秒!如果我在$project
仅包含 6000 个文档的模拟数据库上运行相同的操作,则需要 0.5 秒。为什么会出现这种不匹配以及如何解决?
更新:这不是$project
需要 60 秒的步骤,而是我在它之后放置的任何步骤。例如另一个$match
过滤输出$project
以获得高于 X 的点积。
我正在为 Python 使用 pymongo 库,并且在 Windows 上安装了 MongoDb 版本 v4.4.3。我的集合中有 400 万份文档。每个文件是这样的:
{
_id: ObjectId("...")
document_id: 0,
vector: Array
hash_value: 123
}
wherevector
是一个包含 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
的精确管道(这里将所有 6000 个文档传递到下一步,因为它们都与输入匹配),只需要 0.5 秒!$project
$match
$match
hash_value
似乎我可以通过过滤到可接受的大小来计算快速点积,但是我不能以任何方式使用结果,因为它们加载速度太慢。
我的问题是:
- 为什么会这样?(如果可能的话,我想解释一下幕后发生的事情)
- 有什么办法可以防止这种情况吗?
- 将文档拆分到不同的集合中会有帮助吗?
- 如果您认为使用 MongoDB 无法实现良好的性能,您是否知道其他数据库可以让我对数百万个文档快速执行这种操作?
- 我的数据结构错了吗?为了计算查询和文档之间的点积,是否有更好的方法来构建我的数据?
下面是管道:
{
"$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}}
}
笔记:
- 我使用
$in
是因为实际上可能有多个输入hash_value
- 我已经验证了
$match
第一步实际上是正确过滤掉的 - 我已经看到了这个问题。我遵循了答案中的一些提示(使用allowDiskUse = True,索引我用于该
$match
步骤的字段),但其他评论似乎与我的情况无关。 - 如果我只保留第一个
$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}}
}
推荐阅读
- html - 在 HTML div 上,如何在多次拖放操作后保持 span 的垂直对齐?
- reactjs - 在同一个 React 组件中渲染 React 组件
- mysql - 您可以对每个用户的 mySQL / postgres 进行版本控制吗?
- svn - svn log 只显示文件历史的一个子集
- elasticsearch - 显示和更改参数 threadpool.bulk.queue_size
- typescript - 打字稿:“从不”类型上不存在属性“标题”
- reactjs - 无法从登录重定向到仪表板
- php - 如何从网址中删除“公共”
- c# - “SelectedItem”组合框属性
- react-native - 动态设置 Apollo 客户端标头不起作用