【问题标题】:How can I increase Mongoose/MongoDB create and update performance for a large number of entries如何为大量条目增加 Mongoose/MongoDB 创建和更新性能
【发布时间】:2021-03-22 08:22:27
【问题描述】:

我有一个使用 Mongoose/MongoDB 的 Express 应用程序,我希望找到最有效的批量创建/更新方法(如果可能,全部在单个数据库操作中进行?)。

用户在前端上传 CSV,该 CSV 将转换为 JSON 对象数组并发送到 Express 后端。该数组的范围从 ~3000 个条目到 ~50,000 个以上,并且通常是需要创建的 条目以及需要更新的 现有 条目的组合.每个条目称为一个交易。

这是我目前(不是很高效)的解决方案:

const deals = [
  { deal_id: '887713', foo: 'data', bar: 'data' },
  { deal_id: '922257', foo: 'data', bar: 'data' }
] // each deal contains 5 key/value pairs in the real data array
const len = deals.length
const Model = models.Deal
let created = 0
let updated = 0
let errors = 0
for (let i = 0; i < len; i++) {
  const deal = deals[i]
  const exists = await Model.findOne({ deal_id: deal.deal_id })
  if (exists) {
    exists.foo = deal.foo
    exists.bar = deal.bar
    await exists.save()
    updated += 1
  } else {
    try {
      await Model.create(deal)
      created += 1
    } catch (e) {
      errors += 1
    }
  }
}

目前 findOne/save 或 findOne/create 的组合每次交易大约需要 200-300 毫秒。对于 3000 个条目的低端,需要 10-15 分钟来处理。

如果有帮助,我对绕过 Mongoose 并直接使用 Mongo 并不公正。

如果可能,我希望保持计算更新和创建的项目数量以及错误数量的能力(这在响应中发送以向用户提供一些成功和失败的感觉) - 但这并不重要。

提前致谢! :)

【问题讨论】:

    标签: node.js mongodb express mongoose


    【解决方案1】:

    您希望使用尽可能少的数据库请求来执行此操作。 首先,您可以在一个find 语句中获取所有相关文档。 https://docs.mongodb.com/manual/reference/operator/query/in/

    const deals = [
      { deal_id: '887713', foo: 'data', bar: 'data' },
      { deal_id: '922257', foo: 'data', bar: 'data' }
    ]
    const ids = deals.map(deal => deal.deal_id) // An array of all deal_id
    const documents = await Model.find({ deal_id: { $in: ids }})
    

    现在我们将进行一个查询来更新所有内容,并将属性upsert 设置为truehttps://docs.mongodb.com/manual/reference/method/db.collection.update/ 这将确保如果文档不存在,则自动创建它。

    通过批量更新(同时更新多个),最有效的方法是绕过 mongoose,直接使用命令 bulkWrite 使用 mongodb 驱动程序。 https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/

    const operations = deals.map(deal => {
        updateOne: {
            filter: {
                deal_id: deal.deal_id
            },
            update: {
                $set: deal
            },
            upsert: true
        }
    })
    
    const result = await Model.collection.bulkWrite(operations, { ordered: false })
    

    在上面我还设置了{ ordered: false },它只是告诉 MongoDB“尽可能快地插入,而不考虑我刚刚给你的数组的顺序”。 它还会继续插入其余文档,即使其中一个失败。在 bulkWrite 文档页面下也有说明。

    bulkWrite 的结果对象如下所示

    {
       "acknowledged" : true,
       "deletedCount" : 1,
       "insertedCount" : 2,
       "matchedCount" : 2,
       "upsertedCount" : 0,
       "insertedIds" : {
          "0" : 4,
          "1" : 5
       },
       "upsertedIds" : {
    
       }
    }
    

    这意味着您将获得一份列表,其中包含您获得的匹配数量、更新的匹配数量以及创建的文档 (upsertedIds)。 bulkWrite 的文档中也说明了这一点。

    处理大型数据集的一个好做法是将 bulkWrite 分块为较小的操作数组以提高性能。一个中小型的 MongoDB 服务器应该可以同时处理几千个文档。

    请注意,没有任何代码示例经过测试。但目标是为您指明正确的方向并了解一些良好做法。祝你好运!

    【讨论】:

    • 感谢乔纳森的深入回答,这非常有帮助!我唯一的后续问题是:在bulkWrite 之前进行find 操作的目的是什么?看起来由于bulkWrite 一口气完成了创建和更新,所以不需要初始的find?或者将现有的documents 映射到初始find 之后的deals 然后将结果数组传递给bulkWrite 是否有益?
    • 你说得对,你实际上并不需要查找。但是,如果您出于某种原因想要获取数据以在插入之前映射和比较数据,我就是这样做的。我只是想解释一下如何一次获取多个值。
    猜你喜欢
    • 1970-01-01
    • 2018-05-08
    • 1970-01-01
    • 2018-10-10
    • 1970-01-01
    • 2019-06-09
    • 1970-01-01
    • 2014-05-16
    • 1970-01-01
    相关资源
    最近更新 更多