首页 > 数据库 > 数据库开发技术 > PB级零停机迁移 — PlanetScale
扩展挑战、频繁的维护任务、时刻担心系统何时会崩溃,以及快速上升的成本——这些都是团队为了回避迁移一个已经超出其承载能力的数据库而宁愿忍受的常见问题。
而且理由充分。从一个数据库服务器或系统迁移到另一个存在很多风险:长时间停机、数据丢失、版本不兼容,以及无人能够预料的错误。
但如果我告诉你,这件事不必如此困难呢?在 PlanetScale,我们经常将 TB 级甚至 PB 级的数据库迁移到我们的平台——全程零停机。我们的流程允许我们在切换前使用生产流量进行大量测试,必要时甚至可以将迁移回退到外部数据库。
为了让你了解所涉及的规模,你可以看到其中一个较大数据集的大小——这还只是迄今为止迁移了部分数据库之后的结果——这些数据来源于此处的迁移:

在本文中,我们将深入幕后,展示我们的迁移流程如何运作,以及是什么让我们能够处理大规模数据迁移而无需停机。
在我们深入具体细节之前,先来了解一下高层次的问题,并列出相关术语,以便我们更好地理解和体会其中的细节。
数据迁移是将数据从一个数据库服务器或系统移动到另一个的过程——目标是在未来某个时间点(通常称为切换点)将存储和提供这些数据的责任从旧系统转移到新系统。每个数据库供应商和系统都会为此过程提供一些现成的方案。例如,MySQL 有 mysqldump 和 mysqlimport,而 PostgreSQL(开源关系型数据库) 有 pg_dump 和 pg_restore 用于逻辑数据复制。如果新系统与旧系统类型相同(例如 MySQL 到 MySQL),这两种产品都有方法设置从旧系统到新系统的复制。常见的迁移示例包括跨同一数据库系统的多个大版本升级(例如 PostgreSQL 13 到 16),或从一个数据库系统迁移到另一个(例如 PostgreSQL 到 MySQL 或反之)。
从高层来看,这是数据迁移的一般流程:
对旧系统中的数据创建快照。这可以通过逻辑备份或物理(卷/文件系统/块设备)快照来完成,逻辑备份与存储解耦,并提供了跨不同数据库和存储类型迁移的能力。
将快照恢复到新系统。可以选择重放逻辑备份中的 SQL 语句,或将物理快照恢复到新系统。
验证新系统。至少执行一些基本检查,确保新系统能够读取并提供我们期望看到的数据。
将应用流量切换至新系统。让所有相关应用开始向新系统发送读写流量。
退役旧系统。一旦你对新系统的行为感到满意,就可以完全关闭旧系统,并可选地对其进行归档。
最简单的流程会导致大量停机时间——至少是写入不可用——这涵盖了上述所有步骤,因此从第 1 步开始到第 4 步完成期间,系统处于只读状态。这可能耗时数小时、数天甚至数周。因此,这对于生产系统来说并不总是可接受的,尤其是那些规模较大且需要 7×24 小时可用的系统。此外,如果切换后出现问题——例如某些常用查询可能开始抛出错误或性能远不如前——这种方式也无法回退切换步骤。
理想的流程应允许我们在没有任何停机时间的情况下执行所有这些步骤,并在必要时提供回退切换步骤的能力——即支持双向切换流量,直到我们对最终状态感到满意(只有在此之后的某个时间点才会退役旧系统)。PlanetScale 使用的流程正是为实现这些目标而精心设计的!既然我们已经打好了基础,现在可以开始深入探讨具体细节了。
在 PlanetScale 中,数据迁移在更高层面上是这样进行的(你可以在 PlanetScale 文档中查看用户指南,例如 AWS RDS 迁移指南):
对数据创建一致性非锁定快照,其中包含复制快照创建后所发生变更所需的元数据。我们将在整个迁移过程中持续复制变更。这一点至关重要,因为它意味着你的数据库及使用它的应用的可用性不受迁移过程影响——因此没有停机时间。
数据复制到新系统后,我们继续将变更从旧系统复制到新系统,以便新系统随时准备好进行切换。我们将变更从旧数据库实例复制到 PlanetScale 数据库,以保持第 1 步中的一致性快照与原始系统同步更新——因此没有停机时间。
运行 VDiff 来验证所有数据已正确复制,且新系统与旧系统保持同步,识别切换前需要解决的任何差异。这是一个关键步骤,确保迁移过程中没有出错,所有数据都正确复制且系统保持同步——为最终切换提供信心。
在第 1 步到第 4 步之间的某个时间点,应用开始将流量发送到 PlanetScale 而非直接发送到旧系统。然后 PlanetScale 继续将流量路由回旧系统,直到我们准备好进行切换。这可以在第 6 步执行切换之前的任何时间完成。这允许你测试应用在以 PlanetScale 为目标时的行为,最重要的是,它使得最终的切换快速且透明,因为 PlanetScale 管理路由——因此没有停机时间。警告 你通常希望在接近潜在切换点时执行此步骤。这是因为在切换前的这个阶段,你的查询路由为 应用->PlanetScale->旧数据库,查询响应沿相同路径反向返回。这会产生显著的性能开销——如果应用之前是通过快速本地连接(unix 域套接字、回环设备等)连接到原始数据库,则开销更大。出于同样的原因,在完成切换之前,你不应尝试使用 PlanetScale 进行应用侧的性能比较,将其与使用原始数据库的情况进行对比。这是因为在这个临时的切换前阶段,对于优化良好的查询,大部分总执行时间将花费在网络往返上。但请注意,当使用 PlanetScale Managed 时——这个规模的大多数客户都会这样做——额外的网络开销将是最小的,因为应用和 PlanetScale 数据库通常位于同一云厂商账户中,共享相同的物理位置(区域和可用区)以及这些位置中的物理网络。
你可以根据需要在此状态下保持任意长时间,以准备应用切换并对应用和数据库系统执行额外测试。
透明地将应用流量切换至新系统,使应用流量流向 PlanetScale。在此过程中,我们确保没有数据丢失或漂移。在执行流量切换的系统变更期间,传入查询会被暂停/缓冲。一旦切换完成——通常耗时不到 1 秒——被暂停/缓冲的查询将被执行,系统恢复正常运行。反向复制被部署到位,以便如果因任何原因需要回退切换,我们可以在不丢失数据或停机的情况下完成(可以根据需要来回执行多次)。这里所做的查询缓冲是使整个迁移能够在没有任何停机时间的情况下完成的最后一部分。
当你对系统有信心且不再需要将流量切回旧系统时,你可以完成迁移。这是迁移过程的最后一步,此时你可以随时退役旧系统。
在此过程中的任何时候,旧系统都不会宕机,应用用户也不会察觉到任何异常情况的发生。通常唯一会注意到的是在切换点查询延迟的轻微峰值,此时我们会短暂暂停传入查询,以便在切换工作完成后将它们路由到迁移的正确一侧(旧系统或新系统)。
当你达到一定规模时——在 Vitess 中建议数据库大于 250GiB——水平分片数据库是在不产生指数级更昂贵的硬件相关成本、不影响查询和各种操作(例如 schema 变更和备份)性能的情况下继续扩展的最佳方式。这是 Vitess 和 PlanetScale 的一个关键特性,将数据库分片作为数据迁移的一部分是很常见的做法。例如,你可以有一个未分片的 MySQL 数据库,然后在迁移到 PlanetScale 的过程中将其拆分为 N 个分片。事实上,能够做到这一点本身就是迁移到 PlanetScale 的一个常见原因。
PlanetScale 是一项数据库即服务,提供"现代 MySQL"开发者体验,围绕 Vitess 构建。Vitess 提供了一套与数据迁移相关的工具和原语,称为 VReplication。
基于这组 Vitess 导入原语构建的 PlanetScale 功能被称为(毫不意外地)Database Imports。该功能围绕 VReplication 的 MoveTables 工作流提供了基于 Web 和 CLI 的用户界面,用于一致性数据复制(流式传输行)和复制(流式传输二进制日志事件)。
如果我们再次从更底层逐步梳理 PlanetScale 中的数据迁移过程(这里的步骤有所简化,省略了某些细节和可选行为,但涵盖了主要要点):
通过获取数据的一致性非锁定快照来复制现有数据,该快照包含复制快照获取后发生的更改所需的元数据——我们将在整个迁移过程中持续进行此操作。 a. PlanetScale 数据库集群中的 N 个分片每个都有一个 PRIMARY tablet。这些 tablet 连接到放置在外部 MySQL 实例(旧系统)前面的 unmanaged tablet,并启动一个流,在该流中我们发出 LOCK TABLE
一旦数据已复制到新系统,我们继续将更改从旧数据库实例复制到 PlanetScale 数据库,以便我们随时准备进行切换。同样,从源(MySQL 和 unmanaged tablet 对)到每个目标分片的 PRIMARY tablet 都有一个流。对于每个流,源上的 tablet 将连接到外部 MySQL 实例并启动 COM_BINLOG_DUMP_GTID 协议命令,提供迁移/工作流所拥有的 GTID 已执行快照,该快照对应于复制阶段完成时我们所处的位置(更多详情请参见 Life of a Stream)。每个复制流随后继续根据分片方案过滤这些二进制日志事件,并将它们转发到目标分片的 PRIMARY tablet,在那里应用更改并存储持久化元数据(在 sidecar 数据库的 vreplication 表中)以记录关联的 GTID 集/位置,这样无论发生什么,我们都可以重新启动工作并从上次中断的地方继续。我们持续这样做,直到准备好进行切换。
运行 VDiff 以验证所有数据已正确复制,并且新系统与旧系统同步,识别在切换前需要解决的任何差异(更多详情请参见 Introducing VDiff V2)——同样不会造成任何停机。 注意 工作流中的每个表在 VDiff 完成之前都会串行进行差异比较。因此以下所有步骤都会对工作流中的每个表执行。 a. 我们首先在目标 keyspace 中获取工作流的命名锁——在拓扑服务器中——以防止在我们初始化 VDiff 时对工作流进行任何并发更改,因为我们将操作工作流来停止它、更新它,然后重新启动它。一旦我们在工作流上获得了命名锁,我们就停止工作流以进行步骤 a 到 e 中的表差异初始化。 b. 然后我们连接到源(MySQL 和 unmanaged tablet 对)并在那里启动一致性快照,以与我们在步骤 1a 中进行数据复制时相同的方式用于比较。此时我们在源端拥有了所需的一致性快照。 c. 然后我们使用步骤 b 中的 GTID 位置/快照在每个目标 PRIMARY tablet 上启动流,直到它到达给定位置然后停止(这与标准 MySQL 实例为 START REPLICA UNTIL 所做的相同)。在每个目标分片上,我们随后像在源端一样设置一致性快照。现在我们拥有了源实例上表的一致性快照以及每个目标分片的一致性快照,可用于数据比较。 d. 现在我们重新启动 VReplication 工作流,以便它可以继续将更改从源实例复制到目标分片。 e. 此时,我们完成了对表差异的工作流操作,并释放步骤 a 中获取的目标 keyspace 中工作流的命名锁。 f. 然后我们在源实例和所有目标分片上执行全表扫描,在扫描过程中比较流式结果,记录遇到的任何差异(任一侧缺失的行或值不同的行)——差异的状态持久化在每个目标分片的 sidecar 数据库的 VDiff 表中。您可以跟踪进度,其中包括预计完成时间,完成后您可以查看详细报告,其中注明是否发现任何差异并提供有关这些差异的详细信息——允许您在切换前解决它们(请参见 VDiff show 命令)。 注意 VDiff 默认会在源和目标上选择 REPLICA tablet 进行数据流式传输(工作仍由目标 PRIMARY tablet 编排,状态仍存储在其上),以防止对实时生产系统产生任何影响。VDiff 还具有容错性——如果遇到任何错误,它会自动从上次中断的地方继续——并且可以增量方式进行,例如,如果您处于切换前状态数周甚至数月,您可以运行初始 VDiff,然后在接近切换点时恢复该 VDiff。虽然此步骤不是必需的,但强烈建议在切换前至少运行一次 VDiff,以确保数据已正确复制并且新系统与旧系统同步。
在步骤 1 和 4 之间的某个时间点,应用程序开始将流量发送到 PlanetScale 而不是直接发送到其旧系统。然后 PlanetScale 继续将流量路由回旧系统,直到我们准备好进行切换。模式路由规则被设置好,以便在迁移期间,针对正在迁移的表的查询将被路由到正确的目的地——外部 MySQL 实例(旧系统)或 PlanetScale 数据库(新系统),取决于我们在迁移过程中所处的位置。当迁移开始时,这些规则确保所有查询都发送到源 keyspace(旧系统),并且当流量切换时以及如果切换被逆转时,它们会相应更新。
您可以根据需要保持此状态,同时准备应用切换并对应用和数据库系统执行额外测试。
透明地将应用流量切换到新系统,因为应用流量通过 PlanetScale,PlanetScale 现在将流量路由到新的内部系统而不是旧的外部系统。 a. 在底层,为迁移工作流执行 MoveTables SwitchTraffic 命令。 b. 它首先会进行一些预检查以确保流量切换应该成功,例如检查所涉及 tablet 的整体健康状况、工作流的复制延迟(因为工作流必须在我们可以进行流量切换之前完全赶上源,并且有一个超时,因为这应该是一个短暂的时间段,因为查询正在被缓冲),以及集群中的其他必要状态。如果一切看起来都正常,那么我们将继续进行实际的流量切换。 c. 我们确保源 keyspace 中存在可行的 PRIMARY tablet,这是设置反向 VReplication 工作流所必需的,我们将在流量切换完成时设置该工作流,以便旧系统继续与新系统保持同步,并且如果出于任何原因需要,我们可以将流量切回旧系统。这提供了更大的灵活性和信心,因为如果出现任何意外错误或性能问题(请记住,您可能正在从一个 MySQL 版本甚至 MariaDB 版本迁移到另一个版本,以及从非分片数据库迁移到分片数据库),那么您可以快速还原切换并调查问题。然后一旦问题得到解决,您可以再次尝试将流量切回新系统——无需承受系统停机或需要在任何特定时间完成最终切换的压力。 d. 我们在拓扑服务器中对源和目标 keyspace 加锁,以防止集群中这些 keyspace 的并发更改,同时对目标 keyspace 中的工作流加命名锁,以防止工作流本身的并发更改。 e. 我们停止源 keyspace 上的写入并开始缓冲传入查询(更多详情请参见 VTGate Buffering),以便在流量切换完成后可以在目标 keyspace 上执行它们。 f. 我们等待工作流中的复制完全赶上,以便目标 keyspace 拥有对源执行的所有写入,不会丢失任何内容。 g. 我们创建一个反向 VReplication 工作流,将更改从目标 keyspace(新系统)复制回源 keyspace(旧系统)。这个工作流确保旧系统与新系统上的写入保持同步,以防我们出于任何原因需要还原切换(使用 MoveTables ReverseTraffic 命令)。 h. 我们初始化目标 keyspace 中正在使用的任何 Vitess Sequences。这样做是为了在表作为迁移的一部分进行分片时无缝替换 auto_increment 的使用,以在分片环境中提供自动生成递增唯一值的相同功能。 i. 我们允许对目标 keyspace 进行写入。 j. 我们更新模式路由规则,以便针对正在迁移的表的任何查询现在将被路由到目标 keyspace(新系统)。 k. 我们启动步骤 g 中创建的反向 VReplication 工作流。 l. 我们将原始 VReplication 工作流标记为 Frozen,以便它被隐藏且无法被操作,但我们保留该信息和状态。 m. 我们释放步骤 a 中获取的 keyspace 锁和命名锁。 注意 您可以根据需要保持此状态任意长时间。只有当您对迁移 100% 有信心并且不再需要将流量切回旧系统的选项时,您才可以继续完成迁移,这使用 MoveTables complete 命令来清理工作流及其所有已设置的迁移相关工件(例如路由规则)。更多详情请参见 How Traffic Is Switched。
所有这些工作都是以容错的方式完成的。这意味着在此过程中的任何环节都可能发生故障,而系统能够恢复并从中断处继续执行。这对于达到一定规模的数据导入至关重要,因为这类操作可能需要数小时、数天甚至数周才能完成,而在迁移所涉及的整个进程集群中遇到某种类型错误的可能性——即使是短暂的网络或连接相关错误——也会变得越来越高。
数据迁移是任何数据库系统生命周期中的关键组成部分。有时,为了升级到现有数据库系统的新版本、对现有数据库系统进行分片,或迁移到全新的数据库系统,数据迁移都是必要的。你可能参与过以往导致停机或其他问题的迁移,并且可能正在思考下一次需要进行的迁移以及如何避免这些问题。
通过介绍我们在 PlanetScale 如何执行数据迁移,我们希望你能看到改进自身数据迁移的方法,并避免各种可能导致不良结果的陷阱和问题。我们很乐意帮助你完成下一次数据迁移——无论是作为客户通过我们的工作直接提供帮助,还是作为共享数据库社区的一员,通过像本文这样分享信息和实践来间接提供帮助。

转载声明:本文为转载整理,原文版权归原作者及原站点所有。
原文来源:PlanetScale Blog
原创地址:https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale
原文作者:PlanetScale Blog;如有侵权,请联系管理员删除。