需要在 24 小时内对 2TB 的集合进行分片并在所有分片中分发数据?利用重新分片来加速您的数据迁移!
免责声明:我是 MongoDB 员工,但表达的所有意见和观点都是我自己的
在本文中,我们将介绍一种称为“reshard-to-shard”的技术,它使用重新分片将数据快速分布到集群中的各个分片中。
我们将讨论:
如果您是分片的新手或想复习一下MongoDB如何提供水平可伸缩性,请查看
当集合最初在多分片集群上进行分片时,平衡器开始将数据从保存最近分片集合的分片迁移到集群中的其他分片,以在分片之间平均分配集合。当balancer在迁移数据时,一个shard一次只能参与一个迁移,不管有多少个collection需要迁移。这意味着在一个 3 分片集群中,一次只有两个分片可以在它们之间迁移数据。由于内部执行差异,重新分片不具有相同的限制。
因为重新分片正在重写所有数据,所以它能够跨集群中的所有分片并行写入数据,从而增加吞吐量并大大减少相对于平衡器可以完成的跨分片迁移数据的时间。重新分片会在每个分片的后台使用新的分片键构建一个新集合,同时保持现有集合可供您的应用程序使用。一旦所有文档都被克隆到新集合中,就会发生转换。具有旧分片键的现有集合被删除,以支持通过重新分片操作构建的新集合。
首先,它要快得多!通过利用重新分片,客户能够在 22.5 小时内跨 4 个分片分片和分发他们的 3.5TB 集合。如果留给平衡器的默认块迁移方法,相同的过程将花费 30 天。
其次,它对您的工作量的影响微乎其微!平衡器迁移数据后,它必须执行称为清理操作
第三,你会自动回收你的磁盘空间!通过删除旧集合,它释放了存储空间以供任何集合使用,而无需执行诸如以下的操作
例如,在最大集合的重新分片操作完成之前,客户在 shard0 上消耗了近 2.8TB。
重新分片完成后,1.9TB的存储空间立即归还!他们从消耗 2.7TB 的存储空间变成了 873GB 的存储空间。
答:当您最初将任意大小的集合跨任意数量的分片进行分片时。
在某些情况下,平衡可能会更快(例如小于 100GB),但您仍然必须考虑范围删除和通过压缩或初始同步回收存储。因此,如果您有容量,我们建议您重新分片到分片,无论您要分片的集合有多大。
在以下情况下,您不应使用 reshard-to-shard 策略:
对于正在重新分片的集合,写入可以被阻塞的持续时间默认为两秒,有一个可配置的参数可以修改阻塞持续时间。
对于上面列出的场景,使用分片集合并让平衡器迁移数据的传统方法。
要成功执行重新分片操作,您的集群应该具备:
使用 Atlas 的客户其集群不满足执行重新分片的存储、I/O 和 CPU 要求可以轻松地暂时
执行重新分片到分片操作有两个非常简单的步骤:
为什么我要先分片成一个临时分片键,这不会损害我的应用程序吗?
让我们解释一下!
目前,重新分片不支持重新分片到相同的分片键(它将作为“无操作”成功,因为您已经处于所需状态)。为了绕过这个限制,reshard-to-shard 技术需要有意地分片成一个与所需分片键不同的临时分片键。由于 MongoDB 支持范围分片和哈希分片,临时分片键可以根据您为集合选择的所需分片键进行非常轻微的修改。
临时分片键应该只为您的一个分片键字段选择不同的分区策略。由于某些查询的限制,例如 updateOne()、updateMany()、deleteOne() 等要求查询包含分片键,您将使用不同的分区策略。 MongoDB仅将分区策略用作确定如何在集群中的分片之间分布数据的一种方式,它不会更改文档中的值。这意味着您的应用程序可以使用updateOne
或另一个需要分片键的查询以及两种分区策略。
例如,如果您为集合选择的所需分片键是:
{"_id": "hashed"}
最初用于您的集合的临时分片键应该是:
{"_id": 1}
对于复合分片键,只有一个分片键字段需要使用不同的分区策略。例如,如果您为集合选择的所需分片键是:
{ launch_vehicle: 1, payload: 1}
您的临时分片键应该是:
{ launch_vehicle: 1, payload: "hashed"}
reshard-to-shard 策略要求在使用临时分片键完成集合的初始分片后立即重新分片到您将长期使用的分片键。这会在执行重新分片操作时将超过 99% 的数据保留在一个分片上,从而显着降低广播查询的影响。
如果您想确保 100% 的数据都在一个分片上,您可以在使用临时分片键对集合进行初始分片之前关闭平衡器。禁用平衡器保证在重新分片启动之前不会发生迁移。我们将在下面的示例中介绍如何关闭平衡器。
由于您已经为您的临时和所需的分片键构建了两个索引,在进行重新分片操作时,使用您所需的分片键的查询将是高性能的,因为它们可以在您的集合临时分区时利用所需分片键的索引通过你的临时片键。
第二步是执行正常的重新分片操作,除非您利用重新分片如何为您带来好处的副作用。
重新分片有四个主要阶段:
初始化- 对正在进行重新分片的集合进行采样,并确定基于新分片键的新数据分布。
索引- 重新分片操作基于新的分片键在所有分片上创建一个新的空临时分片集合,并构建索引,包括支持现有集合的非分片键索引。
克隆、追赶和应用- 根据新的分片键将文档克隆到分片,并应用执行重新分片操作时对文档的任何修改。
提交- 临时集合被重命名并取代正在重新分片的集合,现在旧的集合被删除。
回顾上述阶段后,您可以了解如何获得快速数据移动的好处,一旦操作完成,分片集合将平均分布在您的分片中,并一次性释放存储空间。
重新分片操作完成后,您可以执行清理操作,例如删除临时分片键索引并缩减集群和/或存储以满足稳态需求。
假设您正在开发一个跟踪商用飞机的应用程序,以便在客户的航班可能延误时通知客户。您已经研究了您的应用程序的查询模式并查看了哪些属性有助于形成良好的分片键。
您为集合选择的分片键是:
{ airline: 1, flight_number: 1, date: "hashed" }
确定分片键后,您可以开始检查执行重新分片到分片操作的先决条件。首先,您生成临时分片键。如前所述,您希望临时分片键是所需分片键的略微修改版本。
所以你选择的临时分片键是:
{ airline: 1, flight_number: 1, date: 1}
接下来,您构建索引以支持临时和最终分片键。
可以通过db.collection.createIndexes()
命令使用 Mongo shell 创建索引:
db.flight_tracker.createIndexes([ {"airline": 1, "flight_number": 1, date: "hashed"}, {"airline": 1, "flight_number": 1, date: 1} ])
可以通过以下命令使用 mongo shell 监视索引构建:
db.adminCommand({ currentOp: true, $or: [ { op: "command", "command.createIndexes": { $exists: true } }, { op: "none", "msg" : /^Index Build/ } ] })
如果你想确保在分片你的集合和调用重新分片之间没有迁移发生,你可以通过运行以下命令关闭平衡器:
sh.stopBalancer()
如果您的 MongoDB 集群部署在 Atlas 上,您可以使用 Atlas UI 轻松查看可用指标,这些指标会通知您集群有足够的可用可用存储空间以及 CPU 和 I/O 空间来执行重新分片操作。
如果没有足够的可用存储空间或 I/O 空间,您可以扩展存储。如果 CPU 空间不足,您可以扩展集群。通过 Atlas UI 可以轻松扩展存储和集群。
访问集群配置很简单,可以从您组的概览屏幕完成,该屏幕显示该组的所有已部署集群。
满足所有先决条件后,您可以开始重新分片到分片过程的第一部分 - 使用临时分片键分片集合。
然后,您可以使用 Mongo shell 和 sh.shardCollection() 命令对集合进行分片:
sh.shardCollection("main.flight_tracker", { airline: 1, flight_number: 1, date: 1} )
sh.shardCollection() 完成后将返回一个文档,如果操作成功,字段 ok 的值为 1。
{ collectionsharded: 'main.flight_tracker', ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1684160896, i: 25 }), signature: { hash: Binary(Buffer.from("7cb424a56cacd56e47bf155bc036e4a4da4ad6b6", "hex"), 0), keyId: Long("7233411438331559942") } }, operationTime: Timestamp({ t: 1684160896, i: 21 }) }
集合分片后,您可以立即开始重新分片集合!
要将您的集合重新分片到所需的分片键中,请在 mongo shell 中使用 sh.reshardCollection() :
sh.reshardCollection("main.flight_tracker", { airline: 1, flight_number: 1, date: "hashed" })
如果您在 mongo shell 中运行 sh.status() 命令,您将在输出中看到一个新集合,新集合的名称格式为<db_name>.system.resharding.<UUID>
。这是重新分片根据您想要的分片键构建和分发数据的集合
要监控 flight_tracker 集合的重新分片操作的状态,您可以使用以下命令
db.getSiblingDB("admin").aggregate([ { $currentOp: { allUsers: true, localOps: false } }, { $match: { type: "op", "originatingCommand.reshardCollection": "main.flight_tracker" } } ])
该命令的输出将通过 remainingOperationTimeEstimatedSecs 字段通知您当前正在执行重新分片操作的阶段以及预计完成时间。请注意,重新共享的估计完成时间是悲观的,重新分片操作花费的时间比报告的时间少得多!
当每个分片的数据大小增加了被重新分片的集合的大小除以集群中分片的数量时,重新分片操作应该接近完成。例如,一个 1TB 的集合被重新分片到 4 个分片,当每个分片写入 250GB 时,重新分片操作应该完成(不考虑在分片上插入、更新或删除的其他数据)。
如果您的集群部署在 Atlas 上,您还可以使用集群的 Metrics 选项卡通过 Atlas UI 监控重新分片操作的进度。
对于运行 MongoDB 6.0 及更高版本的 Atlas 集群,您可以使用分片数据大小显示选项,然后选择语法为<db_name>.system.resharding.<UUID>
的集合。该视图隔离了临时集合,只会显示新集合的数据大小增长情况。
对于运行 MongoDB 5.0 的 Atlas 集群——您可以使用 db 逻辑数据大小显示选项。此视图不允许集合级别的隔离。
在执行重新分片时,您应该主要监控的集群的三个指标是:
如果您担心重新分片会对您的集群产生负面影响,您可以通过以下命令在它到达流程的提交部分之前立即中止重新分片操作:
sh.abortReshardCollection("main.flight_tracker")
如果您关闭了平衡器以确保在对集合进行分片和重新分片之间没有发生迁移,请立即使用以下命令重新打开平衡器:
sh.startBalancer()
如果操作成功或失败,重新分片操作结束时将返回给调用客户端。
由于重新分片是一个长时间运行的操作,并且您可能已经关闭了那个 Mongo shell 会话,如果您需要详细信息或
您可以使用db.collection.getShardDistribution
来确定操作是否成功:
db.flight_tracker.getShardDistribution()
如果重新分片成功完成,您应该会看到一个输出,其中分片之间的分布相等。
对于 MongoDB 6.0 和更高的均匀度由每个分片的数据大小决定,因此您应该在 db.collection.getShardDistribution 的输出中看到每个分片上的数据量几乎相等。
对于 MongoDB 5.0,均匀度由每个分片的块数决定,因此您应该在 db.collection.getShardDistribution 的输出中看到每个分片上的块数相等。
如果您的集群部署在 Atlas 上,您可以通过 Metrics 选项卡使用 Atlas UI 来确定重新分片操作是否成功。
对于运行 MongoDB 6.0 及更高版本的 Atlas 集群,您可以使用分片数据大小显示选项,然后选择经过重新分片的集合。您应该看到显示的每个分片的数据量相等。
对于运行 MongoDB 5.0 的 Atlas 集群——您可以使用块显示选项,然后选择您的集合进行重新分片。您应该会看到集群中所有分片显示的块数几乎相等。
对于分片数据大小和块数,Atlas UI 将显示相关指标的急剧增加,因为在重命名并删除旧的之前暂时使用集合名称格式<db_name>.system.resharding.<UUID>
使用旧的分片键进行收集。
如果重新分片中止, db.collection.getShardDistribution
的输出可能会显示集合最初分片所在的分片上的大部分数据。重新分片中止的情况很少见,很可能是因为重新分片无法在 2 秒或更短时间内进行集合切换。
如果是这种情况,我们建议安排重新分片的开始时间,以便它尝试在集群流量较低的时期提交。或者,如果您的应用程序可以容忍超过 2 秒的写入阻塞,您可以通过使用 reshardingCriticalSectionTimeoutMillis 参数将持续时间增加到默认值 2000 毫秒以上来修改提交时间。
重新分片操作完成后,您可以执行清理操作,例如删除临时分片键索引并缩减集群和/或存储以满足稳态需求。
您现在可以在 mongo shell 中使用db.collection.dropIndex()
命令删除临时索引!
db.flight_tracker.dropIndex( {"airline": 1, "flight_number": 1, "date": 1} )
重新分片到分片需要多长时间?
这取决于您的集合大小、集合索引的数量和大小以及集群中的分片数量,但我们相信您可以将 4TB 的集合重新分片到分片,其中 48 个分片中的 10 个索引小时或更少。让平衡器处理迁移需要 30 天或更长时间。
为什么重新分片比让平衡器迁移数据更快?
平衡器和重新分片迁移数据的内部机制是不同的。重新分片以不同于块迁移的顺序读取文档,并且由于重新分片以旧集合的丢弃结束,因此无需等待范围删除来释放磁盘空间。
我想在具有唯一性约束且哈希索引不支持强制唯一性的集合上使用 reshard-to-shard。
如果您的集合有唯一性约束,您可以使用重新分片到分片,但必须采用不同的方法。通过向您的临时分片键添加一个额外的字段而不是更改分区策略,您可以解锁重新分片到您想要的分片键的能力。例如,如果您想要的分片键是:
{ launch_vehicle: 1, payload: 1}
您的临时分片键将是:
{ launch_vehicle: 1, payload: 1, launch_pad: 1}
请注意要求查询包含分片键的查询限制(例如:updateOne()、updateMany()、deleteOne())。您的应用程序必须在查询需要分片键才能成功执行直到重新分片操作完成的所有场景中包含临时分片键。
如何监控正在进行的重新分片操作?
运行以下命令:
db.getSiblingDB("admin").aggregate([ { $currentOp: { allUsers: true, localOps: false } }, { $match: { type: "op", "originatingCommand.reshardCollection": "<database>.<collection>" } } ])
如何停止正在进行的重新分片操作?
运行以下命令,它会立即中止重新分片操作:
sh.abortReshardCollection("<database>.<collection>")
我担心重新分片会影响我的集群性能。
如果您满足之前概述的重新分片要求,则该操作不会影响您的集群性能。但是,如果您的集群部署在 Atlas 上,您可以在执行 reshard-to-shard 操作时临时扩展集群,并在操作完成后缩减集群。
在执行重新分片操作时,我应该监控集群的哪些指标?
可用存储空间——如果您的任何分片的可用存储空间少于 100GB,您应该中止重新分片
CPU 利用率 - 如果您的集群正在消耗所有可用的计算资源,您可能会导致资源争用并且应该中止重新分片
I/O 消耗 - 如果您的集群正在消耗所有可用的 IOPS,您可能会导致资源争用并且应该中止重新分片。
临时集合似乎均匀分布在我所有的分片上,为什么重新分片不完整?
在重新分片可以使用您想要的分片键切换到集合之前,它必须
我的应用程序无法承受写入被阻塞超过 1 秒,我可以修改写入被阻塞的持续时间到正在重新分片的集合吗?
是的,但我们不希望经常使用此选项,因为默认情况下重新分片操作会在估计可以在不到 1 秒内完成切换时尝试切换到新集合。如果您的应用程序无法承受对正在重新分片的集合的写入被阻止 2 秒,您可以修改 reshardingCriticalSectionTimeoutMillis 参数以仅阻止写入 1 秒。
运行以下命令:
db.adminCommand({ setParameter: 1, reshardingCriticalSectionTimeoutMillis: 1000 })
在 Pexels 上找到的panumas nikhomkhai的标题照片。