如何处理生产环境中 MongoDB“Schema”更改

29

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

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

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

然后我想给所有嵌套对象添加 _id 字段,这样 mongoose 模式将是:

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

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


从你给出的例子来看,似乎你想要移除 _id,而不是添加它。如果你想要添加一个 _id,你如何确定每个 _id 应该是什么? - Eduardo
我认为通过系统来解决问题。我没有任何ID,我只需要所有的对象都有_ids。 - WHITECOLOR
什么是运行此代码的最佳方式?制作一个独立的node.js模块来连接数据库并进行更改?实施更改的正确方法是什么? - WHITECOLOR
这里最好的方法,因为它看起来像是一个一次性命令,可能是一个node.js模块或在主节点上在MongoDB控制台中运行的js脚本,就像你所说的那样。 - Sammaye
只是一些额外的内容:我会在文档中添加一个“schema_version”来跟踪模式更改。 - boj
显示剩余5条评论
3个回答

20

无模式数据库的一个显著优势是,您不必更新整个数据库以更新模式布局。如果DB中的某些文档没有特定的信息,则您的代码可以执行适当的操作,或选择不对该记录进行任何操作。

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

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


1
抱歉,我不明白你在说什么 _id:false。我非常感兴趣,请你能解释一下吗? - hgoebl
啊,我还没有看问题的文本,抱歉,这不是你的错。但是使用“_id:false”的示例可能会对整个问题有些误导。最好提供一个更容易理解的示例,特别是对于那些不使用Mongoose的人来说。 - hgoebl
1
这个操作会如何影响添加新索引: patientSchema.index({ patientId: 1, institute: 1}, { unique: true }),在开发中我不得不删除旧的没有 { unique: true } 的索引才能让它正常工作。 - Andi Giga

15

确实需要编写脚本来遍历集合并向每个文档添加一个新字段。然而,你如何实现取决于数据库的大小和存储系统的性能。向文档添加字段会改变其大小,在大多数情况下需要重新定位。这个操作对I/O 产生影响,并被其限制。如果你的集合只有几千个文档,最多可能达到十万个,那么你可以在一个循环中迭代它,因为整个集合可能适合内存,所有I/O 将在之后发生。然而,如果集合跨越可用内存,则方法更加复杂。我们通常在MongoDB的生产使用中遵循以下步骤:

  • 打开游标,并将超时设置为False
  • 将一部分文档读入内存
  • 在这些文档上运行更新查询
  • 休眠一段时间,以避免过载I/O子系统并损害生产应用程序
  • 重复直到完成
  • 关闭游标 :)

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


你有光标的简短代码示例吗?我特别感兴趣的是JavaScript版本,因为我认为这并不是微不足道的,尤其是在睡眠一段时间而不会并行执行时... - hgoebl
我没有JavaScript的例子,但在PyMongo驱动程序中,通过将timeout=False传递给find()方法来禁用游标超时。我认为JavaScript驱动程序也会有类似的东西。 - Michael Korbakov
在这种情况下,我可以继续使用 Mongoose 模式吗?我问这个问题是因为每当我们更新模式结构时,Mongoose 模式会自我更新。 - Francis Rodrigues

1

扩展 @Michael Korbakov 的答案,我使用 mongo shell 脚本实现了他的步骤 (参见 MongoDB 参考手册 有关 mongo shell 脚本的内容)。

重要提示:如 MongoDB 参考手册 所述,在 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();

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