Ruby MongodB - 在处理多个集合时提高速度

4
我正在使用mongo gem在Ruby中使用MongoDB。
我有以下场景:
1. 对于集合coll1中的每个文档,查看key1key2 2. 在另一个集合coll2中搜索具有与key1key2匹配值的文档 3. 如果有匹配项,则将#2中获取的文档添加到一个新键key3中,其值设置为#1中引用的文档中key3的值 4. 将更新后的哈希插入到一个新集合coll3
MongoDB的一般准则是在应用程序代码中处理跨集合操作。
所以我做如下操作:
    client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => some_db, 
                               :server_selection_timeout => 5)
    cursor = client[:coll1].find({}, { :projection => {:_id => 0} }) # exclude _id
    cursor.each do |doc|
        doc_coll2 = client[:coll2].find('$and' => [{:key1 => doc[:key1]}, {:key2 => doc[:key2] }]).limit(1).first # no find_one method
        if(doc_coll2 && doc[:key3])
            doc_coll2[:key3] = doc[:key3]
            doc_coll2.delete(:_id) # remove key :_id
            client[:coll3].insert_one(doc_coll2)
        end
    end

这个方法是可行的,但完成这项工作需要很长时间 - 在集合 coll1 中每个文档大约需要250毫秒,或者对于大约15000条记录需要3600秒(1小时),这似乎很多,可能与逐个读取文档,进行应用程序代码检查,然后逐个将文档写回新集合有关。
有没有更快的方法来完成此操作?我现在使用的方法是否正确? 示例文档
  • coll1

    {
        "_id" : ObjectId("588610ead0ae360cb815e55f"),
        "key1" : "115384042",
        "key2" : "276209",
        "key3" : "10101122317876"
    }
    
  • coll2

    {
        "_id" : ObjectId("788610ead0ae360def15e88e"),
        "key1" : "115384042",
        "key2" : "276209",
        "key4" : 10,
        "key5" : 4,
        "key6" : 0,
        "key7" : "false",
        "key8" : 0,
        "key9" : "false"
    }
    
  • coll3

    {
        "_id" : ObjectId("788610ead0ae360def15e88e"),
        "key1" : "115384042",
        "key2" : "276209",
        "key3" : "10101122317876",
        "key4" : 10,
        "key5" : 4,
        "key6" : 0,
        "key7" : "false",
        "key8" : 0,
        "key9" : "false"
    }
    

你能提供coll1和coll2的样本文档以及预期结果的样本文档吗?(即您想要插入到coll3中的文档)? - felix
为coll1、coll2和coll3添加示例 - user3206440
2个回答

7

一种解决方法是使用聚合(aggregation),并在一个单独的查询中执行以下操作:

  • 使用$lookup在key1字段上进行联接(join)
  • 使用$unwind展开数组
  • 使用$redact保留coll1.key2 == coll2.key2的文档
  • 使用$project重新格式化文档
  • 使用$out将其写入coll3

因此,查询应为:

db.coll1.aggregate([
    { "$lookup": { 
        "from": "coll2", 
        "localField": "key1", 
        "foreignField": "key1", 
        "as": "coll2_doc"
    }}, 
    { "$unwind": "$coll2_doc" },
    { "$redact": { 
        "$cond": [
            { "$eq": [ "$key2", "$coll2_doc.key2" ] }, 
            "$$KEEP", 
            "$$PRUNE"
        ]
    }}, 
    { 
      $project: {
         key1: 1, 
         key2: 1, 
         key3: 1, 
         key4: "$coll2_doc.key4",
         key5: "$coll2_doc.key5", 
         key6: "$coll2_doc.key6", 
         key7: "$coll2_doc.key7", 
         key8: "$coll2_doc.key8", 
     key9: "$coll2_doc.key9",  

      } 
    }, 
    {$out: "coll3"} 
], {allowDiskUse: true} );

db.coll3.find() 将返回

{
    "_id" : ObjectId("588610ead0ae360cb815e55f"),
    "key1" : "115384042",
    "key2" : "276209",
    "key3" : "10101122317876",
    "key4" : 10,
    "key5" : 4,
    "key6" : 0,
    "key7" : "false",
    "key8" : 0,
    "key9" : "false"
}

编辑:MongoDB 3.4 解决方案

如果您不想在 $project 阶段指定所有键,您可以利用 MongoDB 3.4 中引入的两个新运算符 $addFields$replaceRoot

查询将变为:

db.coll1.aggregate([
    { "$lookup": { 
        "from": "coll2", 
        "localField": "key1", 
        "foreignField": "key1", 
        "as": "coll2_doc"
    }}, 
    { "$unwind": "$coll2_doc" },
    { "$redact": { 
        "$cond": [
            { "$eq": [ "$key2", "$coll2_doc.key2" ] }, 
            "$$KEEP", 
            "$$PRUNE"
        ]
    }}, 
    {$addFields: {"coll2_doc.key3": "$key3" }},
    {$replaceRoot: {newRoot: "$coll2_doc"}},
    {$out: "coll3"} 
], {allowDiskUse: true})

在聚合操作的 $project 部分,我需要 coll2 中的所有键以及 coll1 中的 key3 - 是否有一种比指定 coll2 中的所有键更简单的方式来指定这些键 - 因为该集合有 200 个键 :( - user3206440
我尝试了这个,它有效。然而就速度的提升而言,我观察到使用应用程序代码每小时处理15000个coll1文档,而使用mongoDb聚合则可以每小时处理37000个coll1文档。这是否符合预期或者时间太长了?对我来说,与典型的关系型数据库相比,完成时间的数量级要长得多。 - user3206440
@user3206440,根据您提供的信息无法回答。这在很大程度上取决于您使用的硬件(磁盘类型、RAM...)以及您的集合内容。您应该查看查询的explain以更好地了解发生了什么。 - felix
我理解您的观点,不过使用同样的机器性能是否符合预期? - user3206440

0

在玩弄这个问题一段时间后,我意识到没有添加索引。添加索引可以将查询运行时间减少数倍。

要添加索引,请执行以下操作。

db.coll1.ensureIndex({"key1": 1, "key2": 1});
db.coll2.ensureIndex({"key1": 1, "key2": 1});

使用索引后,整个查询运行时间缩短到了之前的1/10xxxxxx。

经验教训是,在处理大数据集时,为用于find的字段建立索引 - 这本身就大大减少了查询运行时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接