【问题标题】:mongodb (pymongo) aggregate is slow even after filtering with $match即使在使用 $match 过滤后,mongodb (pymongo) 聚合也很慢
【发布时间】:2021-06-22 16:52:51
【问题描述】:

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

更新:花费 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 步骤的字段),但其他 cmets 似乎与我的情况无关。
  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"),
        ...
}

提前感谢您的慷慨帮助。

【问题讨论】:

  • hash_value 字段是否有索引?
  • @Joe 是的,它是集合中唯一的索引
  • queryVector 和 Vector 数组有多大?
  • @Joe 请检查我对问题的更新,我意识到 $project 步骤非常快,接下来的速度很慢(另一个 $match,或者只是在 python 中迭代结果) .在回答您的问题时,queryVector 和 Vector 是 300 个元素的列表
  • 你是怎么计时的?

标签: mongodb search aggregation-framework pymongo dot-product


【解决方案1】:

我找到了解决方案: 第一个$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}}
}

【讨论】:

    猜你喜欢
    • 2012-08-17
    • 2013-04-20
    • 2020-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多