Docs 菜单
Docs 主页
/
MongoDB Manual
/ / /

对集合重新分片

在此页面上

  • 关于此任务
  • 开始之前
  • 步骤
  • 开始重新分片操作。
  • 监控重新分片操作。
  • 完成重新分片操作。
  • 提前阻止写入以强制完成重新分片
  • 中止重新分片操作
  • 行为
  • 重新分片操作的最短持续时间
  • 可重试写入 (Retryable Writes)
  • 错误案例
  • Duplicate _id Values

版本 5.0 中的新增功能

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

注意

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

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

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

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

    • 可用存储空间:确保集合将分布到的每个分片上的可用存储空间至少是要重新分片的集合大小及其总索引大小的两倍,再除以分片数量。

      storage_req = ( ( collection_size + index_size ) * 2 ) / shard_count

      例如,假设一个集合包含2 TB 数据,并具有分布在四个分片上的400 GB 索引。 要对此集合执行重新分片操作,每个分片都需要1.2 TB 的可用存储空间。

      1.2 TB storage = ( ( 2 TB collection + 0.4 TB index ) * 2 ) / 4 shards

      为了满足存储要求,您可能需要在重新分片操作期间升级到下一个存储层。 操作完成后,您可以缩减规模。

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

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

    重要

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

    • 数据库空间不足并关闭

    • 性能下降

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

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

  • 您必须重写应用程序的查询,才能同时使用当前分分片键和新的分分片键。

    提示

    如果您的应用程序可以容忍停机,则可以执行以下步骤来避免重写应用程序的查询,从而同时使用当前分片键和新的分分片键:

    1. 停止您的应用程序。

    2. 重写应用程序以使用的分分片键。

    3. 等待重新分片完成。 要监控重新分片进程,请使用$currentOp管道阶段。

    4. 部署重写的应用程序。

    在重新分片完成之前,如果查询过滤不包含当前分分片键或唯一字段(如_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 文档。

重要

我们强烈建议您在对集合重新分片之前完整查看“关于此任务”并阅读“步骤”部分。

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

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

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

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

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

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: false,
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 之后,直到分片完全追赶上。

示例,如果remainingOperationTimeEstimatedSecs不减少,您可以使用abortReshardCollection命令中止重新分片操作:

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

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

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

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

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

后退

优化分片键