Docs 菜单

Docs 主页开发应用程序MongoDB Manual

对集合重新分片

在此页面上

  • 要求
  • 限制
  • 对进程重新分片
  • 开始重新分片操作。
  • 监控重新分片操作。
  • 完成重新分片操作。
  • 提前阻止写入以强制完成重新分片
  • 中止重新分片操作
  • 行为
  • 重新分片操作的最短持续时间
  • 可重试写入 (Retryable Writes)
  • 错误案例
  • 主节点故障转移
  • 重复 _id

版本 5.0 中的新增功能

理想的分片键可以让 MongoDB 在整个集群中平均分配文档,同时也有利于实施常见的查询模式。由于数据分配不平均,次优的分片键可能会导致性能或扩展问题。从 MongoDB 5.0 开始,您可以更改集合的分片键以更改集群中的数据分配情况。

注意

在对集合重新分片之前,请阅读分片键疑难解答,了解有关常见性能和扩展问题的信息以及如何修复这些问题的建议。

在对集合重新分片之前,请确保满足以下要求:

  • 您的应用程序可以允许正在重新分片的集合在两秒钟内阻止写入。在写入受阻期间,应用程序的延迟会增加。如果您的工作负载无法允许此要求,请考虑改为改进分片键

  • 您的数据库符合这些资源要求:

    • 可用存储空间:确保集群具有足够的可用磁盘空间用于重新分片。如果重新分片集合中的文档均匀分布在集群中,则每个分片需要的磁盘空间约为磁盘上集合大小除以分片数量所得数字的 1.2 倍。

      注意

      重新分片需要额外的磁盘空间,因为该进程会根据新的分片键使用跨分片分布的数据创建新集合。有关更多信息,请参阅重新分片进程

    • I/O:确保 I/O 容量低于 50%。

    • CPU 负载:确保 CPU 负载低于 80%。

    重要

    数据库不会强制执行这些要求。未能分配足够的资源可能会导致:

    • 数据库空间不足并关闭

    • 性能下降

    • 重新分片操作花费的时间比预期要长

    如果您的应用程序存在流量较少的时间段,请尽可能在该时间段内对集合重新分片。

  • 您必须执行其中一项任务:

    如果查询筛选器不包含当前分片键或唯一字段(如_id),以下查询会返回错误:

    为了获得最佳性能,我们还建议您重写其他查询以包含新分片键。

    重新分片操作完成后,您可以从查询中删除旧分片键。

  • 没有正在运行的索引构建。使用 db.currentOp() 检查是否有任何正在运行的索引构建:

    db.adminCommand(
    {
    currentOp: true,
    $or: [
    { op: "command", "command.createIndexes": { $exists: true } },
    { op: "none", "msg" : /^Index Build/ }
    ]
    }
    )

    在结果文档中,如果 inprog 字段值为空数组,则表示没有正在运行的索引构建:

    {
    inprog: [],
    ok: 1,
    '$clusterTime': { ... },
    operationTime: <timestamp>
    }

注意

重新分片是一个写入密集型进程,可以提高 oplog 的速率。您可能希望:

  • 设置固定的 oplog 大小以防止 oplog 无限增长。

  • 增加 oplog 大小以最大限度地减少一个或多个从节点过时的可能性。

有关更多详细信息,请参阅副本集 Oplog 文档。

  • 一次只能对一个集合重新分片。

  • writeConcernMajorityJournalDefault 必须为 true

  • 要对具有唯一性约束的集合重新分片,新分片键必须满足任何现有唯一索引的唯一索引要求

  • 在重新分片操作正在进行时,正在重新分片的集合不支持以下命令和相应的 shell 方法:

  • 重新分片操作正在进行时,集群上不支持以下命令和方法:

    警告

    在重新分片操作期间使用上述任何命令都会导致重新分片操作失败。

  • 如果要重新分片的集合使用Atlas Search ,则在重新分片操作完成后,搜索索引将变为不可用。重新分片操作完成后,您需要手动重建搜索索引。

重要

我们强烈建议您在对集合重新分片之前,完整查看“限制”并阅读“重新分片进程”部分。

在集合重新分片操作中,分片可以是:

  • 发送分片,它目前存储分片集合的数据段

  • 接收分片,它根据分片键区域存储分片集合的新数据段。

分片可以同时是发送分片和接收分片。除非您使用区域,否则发送分片集与接收分片相同。

配置服务器主节点始终是重新分片协调器,并启动重新分片操作的每个阶段。

1

连接到 mongos 时,发出 reshardCollection 命令,指定要重新分片的集合和新分片键:

db.adminCommand({
reshardCollection: "<database>.<collection>",
key: <shardkey>
})

MongoDB 将阻止写入的最大秒数设置为两秒,并开始重新分片操作。

2

要监控重分片操作,可以使用 $currentOp 管道阶段:

db.getSiblingDB("admin").aggregate([
{ $currentOp: { allUsers: true, localOps: false } },
{
$match: {
type: "op",
"originatingCommand.reshardCollection": "<database>.<collection>"
}
}
])

注意

要查看更新的值,您需要连续运行前面的管道。

$currentOp 管道输出:

  • totalOperationTimeElapsedSecs:经过的操作时间(以秒为单位)

  • remainingOperationTimeEstimatedSecs:当前重新分片操作的预计剩余时间(以秒为单位)。当新的重新分片操作开始时,它会以-1返回。

    开始于:

    • MongoDB 5.0,但在 MongoDB 6.1 之前,remainingOperationTimeEstimatedSecs 仅在重新分片操作期间在接收分片上可用。

    • MongoDB 6.1,在重新分片操作期间,协调器上也可以使用 remainingOperationTimeEstimatedSecs

    重新分片操作按顺序执行以下阶段:

    1. 克隆阶段复制当前的集合数据。

    2. 追赶阶段将所有待处理的写入操作应用于重新分片的集合。

    remainingOperationTimeEstimatedSecs 设置为悲观的时间估计值:

    • 将追赶阶段时间估计值设置为克隆阶段时间,这是一个相对较长的时间。

    • 实际上,如果只有几个待处理的写入操作,则实际的追赶阶段时间相对较短。

[
{
shard: '<shard>',
type: 'op',
desc: 'ReshardingRecipientService | ReshardingDonorService | ReshardingCoordinatorService <reshardingUUID>',
op: 'command',
ns: '<database>.<collection>',
originatingCommand: {
reshardCollection: '<database>.<collection>',
key: <shardkey>,
unique: <boolean>,
collation: { locale: 'simple' }
},
totalOperationTimeElapsedSecs: <number>,
remainingOperationTimeEstimatedSecs: <number>,
...
},
...
]
3

在整个重新分片进程中,完成重新分片操作的预计时间 (remainingOperationTimeEstimatedSecs) 会减少。当估计时间低于两秒时,MongoDB 会阻止写入并完成重新分片操作。在完成重新分片操作的估计时间低于两秒之前,重新分片操作默认不会阻止写入。在写入受阻期间,应用程序的延迟会增加。

重新分片进程完成后,重新分片命令会返回 ok: 1

{
ok: 1,
'$clusterTime': {
clusterTime: <timestamp>,
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: <number>
}
},
operationTime: <timestamp>
}

要查看重新分片操作是否成功完成,请检查 sh.status() 方法的输出:

sh.status()

sh.status() 方法输出包含小部分 databases。如果重新分片成功完成,输出会列出集合的新分片键钥:

databases
[
{
database: {
_id: '<database>',
primary: '<shard>',
partitioned: true,
version: {
uuid: <uuid>,
timestamp: <timestamp>,
lastMod: <number>
}
},
collections: {
'<database>.<collection>': {
shardKey: <shardkey>,
unique: <boolean>,
balancing: <boolean>,
chunks: [],
tags: []
}
}
}
...
]

注意

如果重新分片集合使用Atlas Search ,则在重新分片操作完成后,搜索索引将变为不可用。重新分片操作完成后,您需要手动重建搜索索引。

您可以通过发出 commitReshardCollection 命令手动强制完成重新分片操作。如果完成重新分片操作的当前时间估计是您的集合阻止写入的可接受持续时间,则这非常有用。commitReshardCollection 命令会阻止提前写入并强制完成重新分片操作。该命令具有以下语法:

db.adminCommand({
commitReshardCollection: "<database>.<collection>"
})

您可以在重新分片操作的任何阶段中止重新分片操作,甚至在运行 commitReshardCollection 之后,直到分片完全追赶上。

例如,如果写入不可用持续时间估计值没有减少,则可以使用 abortReshardCollection 命令中止重新分片操作:

db.adminCommand({
abortReshardCollection: "<database>.<collection>"
})

取消操作后,可以在写入量较低的时间窗口内重试重新分片操作。如果无法做到这一点,请在重试前添加更多分片

重新分片操作的最短持续时间始终为 5 分钟。

在重新分片之前或期间启动的可重试写入可以在集合重新分片期间和之后重试最多 5 分钟。5 分钟后,您可能无法找到写入的最终结果,并且后续尝试重试写入会失败,并引发 IncompleteTransactionHistory 错误。

如果副本集分片或配置服务器上发生主节点故障转移,则重新分片操作将中止。

如果重新分片操作由于主节点故障转移而中止,请在开始新的重新分片操作之前运行cleanupReshardCollection命令:

db.runCommand({
cleanupReshardCollection: "<database>.<collection>"
})

如果 _id 值不是全局唯一的,则重新分片操作会失败,以避免损坏集合数据。重复的 _id 值也会阻止成功的数据段迁移。如果您的文档具有重复的 _id 值,请将每个文档中的数据复制到新文档中,然后删除重复的文档。

← 优化分片键