0Pricing
MongoDB Academy · 课时

无停机演进模式

您将更新运行中集合的现有验证器,并编写迁移脚本,将文档补全为新的结构。

无停机演进模式 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

The Challenge of Schema Evolution

As your application grows, requirements change and your MongoDB schema must evolve. Unlike relational databases, you cannot run a blocking ALTER TABLE that locks the entire table during migration. MongoDB's flexibility means old and new document shapes can coexist in the same collection, which requires a deliberate migration strategy to keep the application working while the schema transitions.

Step 1: Update the Validator in Warn Mode

Begin every schema migration by updating the collection's validator to reflect the new schema, but with validationAction: 'warn'. This allows existing non-conforming documents to remain and be updated without errors while you measure how many documents need to be backfilled. New writes from updated application code will conform to the new schema.

// Add a new required field 'phoneNumber' to the validator
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['name', 'email', 'phoneNumber'],
      properties: {
        name:        { bsonType: 'string' },
        email:       { bsonType: 'string' },
        phoneNumber: { bsonType: 'string' }
      }
    }
  },
  validationLevel: 'moderate',
  validationAction: 'warn'
});

Step 2: Update the Application Code

Deploy the updated application code that writes documents conforming to the new schema. New documents will now include the new fields. Old documents written before the schema change remain in the collection in their original shape. During this phase, your application code must handle both document shapes—for example, by providing a default value when the new field is absent.

// Application code that handles both old and new document shapes
async function getUserPhone(userId) {
  const user = await db.collection('users').findOne({ _id: userId });
  // Provide a fallback for documents written before the migration
  return user.phoneNumber || 'Not provided';
}

Step 3: Write a Backfill Migration Script

A backfill script iterates over all documents missing the new field and sets a default value. Run it in small batches to avoid locking resources or spiking server load. Use bulkWrite with ordered: false for efficiency, and track progress with logging so you can resume if interrupted.

// Backfill: set phoneNumber to '' for documents that lack it
const collection = db.collection('users');
const cursor = collection.find({ phoneNumber: { $exists: false } });

const batchSize = 500;
let batch = [];

for await (const doc of cursor) {
  batch.push({
    updateOne: {
      filter: { _id: doc._id },
      update: { $set: { phoneNumber: '' } }
    }
  });
  if (batch.length === batchSize) {
    await collection.bulkWrite(batch, { ordered: false });
    console.log('Processed', batchSize, 'docs');
    batch = [];
  }
}
if (batch.length) await collection.bulkWrite(batch, { ordered: false });
console.log('Backfill complete');

Step 4: Switch to Strict Error Mode

After the backfill is complete and you have verified that all documents conform to the new schema, switch validationLevel to strict and validationAction to error. From this point on, any write that violates the schema is rejected. Monitor the application for unexpected errors in the first hours after switching to catch any edge case that the backfill missed.

// Enable full enforcement after backfill is verified
db.runCommand({
  collMod: 'users',
  validationLevel: 'strict',
  validationAction: 'error'
});
console.log('Full schema enforcement enabled');

Renaming a Field Safely

To rename a field (e.g., fullName → name), first add name to new documents while the application still reads fullName as a fallback. Then backfill by copying fullName to name using $rename or $set. Finally, update the application to write and read only name, and remove fullName from old documents.

// Backfill: rename fullName to name for all existing documents
db.users.updateMany(
  { fullName: { $exists: true }, name: { $exists: false } },
  [{ $set: { name: '$fullName' } }, { $unset: 'fullName' }]
);

The Schema Versioning Pattern

For complex long-running migrations, add a schemaVersion field to every document. Application code checks this field and applies a transformation function for each older version before processing the document. New writes always include the latest schemaVersion. This gives you a controlled, auditable upgrade path and allows multiple schema generations to coexist indefinitely.

// Insert new document with schema version
db.products.insertOne({
  schemaVersion: 2,
  name: 'Widget Pro',
  priceUSD: 49.99,
  categories: ['electronics']
});

// Application transformer
function normalise(doc) {
  if (doc.schemaVersion === 1) {
    // v1 used 'price' instead of 'priceUSD'
    doc.priceUSD = doc.price;
    delete doc.price;
    doc.schemaVersion = 2;
  }
  return doc;
}

Adding a New Optional Field Safely

Adding a new optional field is the simplest schema evolution: update the validator's properties without adding it to required. Existing documents simply don't have the field, and the validator passes them because the field is not required. No backfill is necessary. New documents can include the field; application code reads it with a safe fallback default.

// Add optional 'avatarUrl' field to the validator
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['name', 'email'],
      properties: {
        name:      { bsonType: 'string' },
        email:     { bsonType: 'string' },
        avatarUrl: { bsonType: 'string' }  // optional — no backfill needed
      }
    }
  }
});

Removing a Field From the Schema

To retire a field, first remove it from required if it was required, then deploy application code that no longer writes the field. Over time, new documents won't contain the field. You can optionally backfill by removing the field from all existing documents using $unset, but this is only necessary if the field wastes storage or causes confusion.

// Remove the deprecated 'legacyCode' field from all documents
db.products.updateMany(
  { legacyCode: { $exists: true } },
  { $unset: { legacyCode: '' } }
);

Zero-Downtime Migration Summary

The key insight for zero-downtime schema migration is that MongoDB allows mixed document shapes in the same collection. This means you never need to take the database offline to change the schema. The four-phase playbook—warn-mode validator, app code update, backfill, strict enforcement—decouples the schema change from the deployment and gives you full control over the transition timeline.

Verifying Migration Completeness

Before switching to strict mode, verify that zero documents are missing the required fields. A simple count query confirms completeness. If any documents are still non-conforming, run the backfill script again. This verification step prevents surprise validation errors after switching to error action.

// Verify no users are missing the new required field
const missing = await db.collection('users').countDocuments({
  phoneNumber: { $exists: false }
});
console.log('Documents missing phoneNumber:', missing);
// Should be 0 before enabling strict enforcement

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: the four-phase migration playbook (warn → app update → backfill → strict) achieves zero downtime, the schema versioning pattern lets multiple document shapes coexist indefinitely, and verification before strict enforcement prevents surprise errors. Next up we explore projection and field selection to fetch only the fields your queries actually need.

常见问题解答

「无停机演进模式」课时是免费的吗?

是的 — 「无停机演进模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。

「无停机演进模式」这节课中我会学到什么?

您将更新运行中集合的现有验证器,并编写迁移脚本,将文档补全为新的结构。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 MongoDB Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「无停机演进模式」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 MongoDB Academy 课中编写并运行代码吗?

能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为集合添加验证器
  2. 类型、必填与枚举约束
  3. 验证级别与操作
  4. 无停机演进模式
← 返回 MongoDB Academy