【问题标题】:How to handle mongodb "schema" change in production如何处理生产中的mongodb“模式”变化
【发布时间】:2012-12-26 18:25:05
【问题描述】:

我使用 mongodb + node.js + mongoose.js ORM 后端。

假设我有一些没有 _id 字段的嵌套对象数组

mongoose.Schema({
  nested: [{
    _id: false, prop: 'string'
  }]
})

然后我想将 _id 字段添加到所有嵌套的对象中,所以猫鼬模式是

mongoose.Schema({
  nested: [{
    prop: 'string'
  }]
})

那么我应该运行一些脚本来修改生产数据库,对吗?处理这种变化的最佳方法是什么?哪种工具(或方法)最适合用于实施变革?

【问题讨论】:

  • 从您给出的示例中,您似乎想要删除_id,而不是添加它。如果要添加一个_id,如何确定每个_id应该是什么?
  • 我不明白你。 _id: false 告诉 mongoose 不要为模式描述的对象生成 _id,如果我从模式描述中删除 _id: false,mongoose 将使用生成的 _id 创建新文档。我要问的是用新的_ids填充所有现有对象(没有_id)的正确方法。
  • _ids应该由系统生成还是由你生成?
  • 我认为是系统。我没有任何 ID,我只需要所有对象都有 _id。
  • 您需要逐个元素地手动编写代码。

标签: node.js mongodb mongoose


【解决方案1】:

扩展@Michael Korbakov 的回答,我用mongo shell 脚本实现了他的步骤(参见MongoDB Reference Manual 关于mongo shell 脚本)。

重要提示:如MongoDB Reference Manual 中所述,在mongo shell 上运行脚本可以提高性能,因为它减少了每次批量获取和批量执行的连接延迟。

应该考虑的一个缺点是mongo shell 命令始终是同步的,但是批量执行已经为我们处理了并行性(对于每个块),因此我们很适合这个用例。

代码:

// constants
var sourceDbName = 'sourceDb';
var sourceCollectionName = 'sourceColl';
var destDbName = 'destdb';
var destCollectionName = 'destColl';
var bulkWriteChunckSize = 1000;
// for fetching, I figured 1000 for current bulkWrite, and +1000 ready for next bulkWrite
var batchSize = 2000;    
var sourceDb = db.getSiblingDB(sourceDbName);
var destDb = db.getSiblingDB(destDbName);

var start = new Date();

var cursor = sourceDb[sourceCollectionName].find({}).noCursorTimeout().batchSize(batchSize);

var currChunkSize = 0;
var bulk = destDb[destCollectionName].initializeUnorderedBulkOp();
cursor.forEach(function(doc) {
    currChunkSize++;
    bulk.insert({
        ...doc,
        newProperty: 'hello!',
    }); // can be changed for your need, if you want update instead

    if (currChunkSize === bulkWriteChunckSize) {
        bulk.execute();

        // each bulk.execute took for me 130ms, so i figured to wait the same time as well
        sleep(130);

        currChunkSize = 0;
        bulk = destDb[destCollectionName].initializeUnorderedBulkOp();
    }
});

if (currChunkSize > 0) {
    bulk.execute();
    currChunkSize = 0;
}

var end = new Date();
print(end - start);

cursor.close();

【讨论】:

    【解决方案2】:

    您确实必须编写将遍历集合并向每个文档添加新字段的脚本。但是,具体的操作方式取决于数据库的大小和存储系统的性能。在文档中添加一个字段会改变它的大小,因此在大多数情况下会导致重定位。该操作对 IO 有影响,也受其约束。如果您的集合只有几千个文档,可能多达十万个,那么您可以只在一个循环中迭代它,因为整个集合可能适合内存,并且所有 IO 都将在之后发生。但是,如果收集的范围远远超出可用内存,则该方法会更加复杂。我们通常在 MongoDB 的生产使用中遵循以下步骤:

    • 用 timeout=False 打开游标
    • 将一大段文档读入内存
    • 对这些文档运行更新查询
    • 休眠一段时间,以避免 IO 子系统过载并损害生产应用程序
    • 重复直到完成
    • 关闭光标:)

    文档块的大小和休眠期必须通过实验确定。通常,您希望在迁移期间避免 mongostats 中的 QR/QW。对于较慢驱动器上的大型集合(例如 Amazon 上的 EBS),这种 IO 安全方法可能需要数小时到数天的时间。

    【讨论】:

    • 你有光标的简短代码示例吗?我对JavaScript版本特别感兴趣,因为我觉得这不是小事,尤其是睡了一段时间没有得到并行...
    • 我没有 JavaScript 示例,但在 PyMongo 驱动程序中,通过简单地将 timeout=False 传递给 find() 方法来禁用光标超时。我认为 JavaScript 驱动程序会有这样的东西。
    • 在这种情况下,我可以继续使用Mongoose 架构吗?我问它是因为 Mongoose 模式会自我更新,我们总是会更新模式结构。
    【解决方案3】:

    无架构数据库的显着优势之一是您不必使用新架构布局更新整个数据库。如果数据库中的某些文档没有特定信息,那么您的代码可以改为执行适当的操作,或者选择现在对该记录执行任何操作。

    另一种选择是根据需要延迟更新文档 - 仅在再次查看文档时。在这种情况下,您可能会选择使用每个记录/文档的版本标志 - 它最初甚至可能不会出现(因此表示“版本 0”)。即使那是可选的。相反,您的数据库访问代码会查找它需要的数据,如果它不存在,因为它是在代码更新后添加的新信息,那么它将尽其所能填充结果。

    对于您的示例,将 _id:false 转换为标准 MongoId 字段,当代码被读取(或在更新后写回)并且当前设置了 _id:false 时,然后进行更改并写入仅在绝对需要时。

    【讨论】:

    • 抱歉,我不明白你对_id:false 的意思。我真的很感兴趣。请解释一下好吗?
    • 啊,我没有看问题文字,对不起,这不是你的错。但是_id:false 的例子可能对整个问题有点误导。最好有一个对所有人都更好理解的示例,尤其是对于那些不使用 Mongoose 的人。
    • 添加新索引之类的操作会怎样:patientSchema.index({ patientId: 1, institute: 1}, { unique: true }),在开发中我必须删除没有{ unique: true } 的旧索引才能使其正常工作
    猜你喜欢
    • 2019-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多