MongoDB中的可扩展性,具有数千个查询



我有一个运行没有错误的代码,但由于MongoDB中的查询数量,它真的很慢。

我有一个很大的python列表(~5000个项目(,我想测试MongoDB文档中何时同时存在两个项目。

mylist = ['apple','banana','melon','orange']
for i in mylist:
   for j in mylist[mylist.index(i):]:
       test = db.collection.find_one({'mylabel':{'$all':[i,j]}})
       if test:
          then do my stuff

其中mylabel也是一个列表,其中包含来自mylist的项目。

在 150-200 个项目的列表中,只需几分钟即可完成。但是在像我这样的列表中,即使有更多的项目,我也需要几个小时。有没有办法在时间基础上改进它?

根据要求编辑:

我还没有创建索引。这是我数据库中结构的示例(但是它太简单了(

{
 "_id": 1,
 "postid": 1,
 "mylabel":['apple','banana','melon']
}
{
 "_id": 2,
 "postid": 2,
 "mylabel":['banana','melon']
}

_id是MongoDB的标准id,postid是我给文档的自定义id,mylabel是标签列表。

因此,在获得所有必需的信息后,这是我的答案:

  1. 您需要在字段上有一个索引 myLabel .这是你这样做的方式:db.collection.ensureIndex({'myLabel': 1});我希望这会加快查询速度。这是一种标准技术。
  2. 除此之外,您还可以尝试将整个集合放入内存中。或者,如果您的内存不够,您可以升级它以获得更快的性能。
  3. 如果您的收藏非常大,则可以对其进行分片。这将在查询中包含一些并行性。

就这段代码的复杂性而言,你不能比 O(n^2( 做得更好。

对于数组中的 5000 个项目来说可能不是最好的办法(你可以试试(,但如果你有一个 MongoDB 2.6 或更高版本,在聚合框架中有一些运算符,你至少可以加快速度。

只是为了简单起见,在 JavaScript 中注明,无论如何与 python 代码没有太大区别

var mylist = ['apple','banana','melon','orange'];
db.collection.aggregate([
    // Match possible documents excluding single element and empty
    // arrays that cannot possibly match
    { "$match": {
        "$and": [
            { "mylabel": { "$in": mylist } },
            { "mylabel": { "$not": { "$size": 1 } } }
        ]
    }},
    // Project the size of the set-intersection
    { "$project": {
        "postid": 1,
        "mylabel": 1,
        "size": { "$size": { "$setIntersection": [ "$mylabel", "mylist" ] } }
    }},
    // Match only the documents with "size" greater than or equal to 2
    { "$match": { "size": { "$gte": 2 } }
])

不言自明,匹配包含至少一个匹配列表项的文档,并且必须具有自己的数组大小为 2 或更大才能生成可能的匹配项。然后$setIntersection运算符进来比较数组并找到相同的项目。生成的数组使用 $size 进行测试,在此上下文中, 是一个聚合运算符,用于确定结果数组的大小。

最终$match过滤掉任何未报告匹配的 2 个或更多项目的"大小"的结果。因此,这些文档包含匹配列表中至少两个项目。这应该真正过滤掉结果,实际上是你所要求的。

看看它是否适合您的匹配列表中的 5000 个元素,或者尝试拆分您的匹配列表。它不应该打破BSON的限制,虽然我认为在性能方面比较不会很好,但它仍然不应该花费几个小时。

当前流程中最大的时间问题是需要为两者的所有组合触发所有查询。这里的重点是否定网络流量并让引擎完成工作。

此外,您的find_one方法也存在缺陷,因为任何配对组合无疑都有不止一个匹配项。这意味着您当前的流程可能会错过实际符合条件的文档。

另外,请向数组字段添加索引。虽然此处的$match语句不会自行过滤所有内容,但可能至少可以过滤掉集合中的某些文档,索引将有助于避免扫描集合中的每个文档。

最后,结果可能仍然是大量文档,因此建议考虑使用"光标"作为输出方法,而不是默认的,这是一个大列表。有关语法和示例,请参阅有关聚合的 pymongo 文档。


如果不是在MongoDB 2.6或更高版本上,则可能是最安全的选择:

var mylist = ['apple','banana','melon','orange'];
db.collection.mapReduce(
    function () {
        result = mylist.filter(function(n) {
            return this.mylist.indexOf(n) != -1
        });
        if ( result.length >= 2 )
            emit( this._id, this );
    },
    function(){},
    {
        "query": {
            "$and": [
                { "mylabel": { "$in": mylist } },
                { "mylabel": { "$not": { "$size": 1 } } }
            ]
        },
        "out": "outputcollection",
        "scope": { "mylist": mylist }
    }
)

同样,查找与大于 2 的"设置交集"大小匹配的文档。"scope"和"query"中的参数将只是将序列化为请求的python列表。

最新更新