
涉及 MySQL 的多个领域,包括客户端连接器、存储引擎、优化器和复制。每个新版本 MySQL 都需要我们投入大量时间和精力来迁移工作负载,主要挑战:
上次重大版本升级到 MySQL 5.6 花费一年多。当 5.7 发布时,我们还在基于 5.6 开发 LSM-Tree 存储引擎 MyRocks。因为在开发新存储引擎的同时升级到 5.7 会显著拖慢 MyRocks 的进展,我们选择在完成 MyRocks 之前继续使用 5.6。当我们将 MyRocks 部署到用户数据库(UDB)服务层的过程接近尾声时,MySQL 8.0 发布了。
此版本带来了一些重要功能,如基于写集合的并行复制和支持事务性数据字典的原子 DDL。迁移到 8.0 还将让我们获得之前未升级到 5.7 时错过的功能,比如 Document Store。随着 5.6 临近生命周期终点,我们希望继续在 MySQL 社区中保持活跃,尤其是与 MyRocks 存储引擎相关的工作。8.0 的改进功能(如即时 DDL)能够加快 MyRocks 架构变更,但我们需要迁移到 8.0 代码库才能使用这些功能。综合考虑后,我们决定迁移到 8.0。在此,我们分享了如何完成 8.0 的迁移项目,以及过程中发现的一些意外挑战。在最初评估项目时,我们发现迁移到 8.0 的难度甚至超过了之前的迁移工作,如迁移到 5.6 或开发 MyRocks。
我们首先在开发环境中搭建了 8.0 分支,用于构建和测试。随后,我们开始了将 5.6 分支的补丁移植到 8.0 的漫长工作。起初有超过 1,700 个补丁需要移植,但我们将其归为几个主要类别。大部分自定义代码都有详细的注释和说明,这使得我们可以轻松判断这些功能是否仍被应用程序需要,或是否可以移除。通过特殊关键字或唯一变量名启用的功能也很容易通过在应用代码库中进行搜索来确定其相关性。一些补丁非常晦涩,需要我们深入挖掘,例如查阅旧的设计文档、帖子以及代码审查记录,以了解其历史背景。
我们将补丁分为以下四类:
我们使用电子表格记录每个补丁的状态及相关历史信息,并在移除补丁时记录具体理由。多个补丁若涉及相同功能,会分组处理后再移植。移植并提交到 8.0 分支的补丁会标注对应的 5.6 提交信息。由于需要处理的大量补丁,移植状态偶尔会出现不一致,但这些记录帮助我们解决了这些问题。
客户端和服务器类别的每一部分功能自然形成了一个软件发布里程碑。完成所有客户端相关更改后,我们成功将客户端工具和连接器代码更新至 8.0。一旦非 MyRocks 服务器功能移植完成,我们便可部署 8.0 mysqld 用于 InnoDB 服务器。最后,完成 MyRocks 服务器功能的移植后,我们便可更新 MyRocks 安装。
一些最复杂的功能在 8.0 中需要进行重大更改,部分领域甚至存在严重的兼容性问题。例如,上游 8.0 的 binlog 事件格式与我们自定义的 5.6 修改存在不兼容。Facebook 5.6 功能中使用的错误代码与上游 8.0 中为新功能分配的错误代码发生冲突。最终,我们需要修补 5.6 服务器以实现对 8.0 的前向兼容。
完成所有功能移植耗时两年。在此期间,我们评估了超过 2,300 个补丁,并最终将其中的 1,500 个移植到了 8.0。
将多个 mysqld 实例分组为一个 MySQL 副本集。每个副本集的实例包含相同的数据,但分布在不同的地理数据中心,以确保数据的可用性并支持故障转移。每个副本集中有一个主实例,其余为从实例。主实例处理所有写入操作,并将数据以异步方式复制到所有从实例。
目标是将当前由 5.6 主实例和 5.6 从实例组成的副本集迁移为由 8.0 主实例和 8.0 从实例组成的副本集。采取类似 UDB MyRocks 迁移计划的方法:
每个副本集可以独立按照上述步骤迁移,并且可以在某一步停留足够的时间。我们将副本集划分为更小的组,逐步完成迁移。遇到问题时,可以回滚到上一阶段。在某些情况下,一些副本集能够在其他副本集开始迁移之前完成所有步骤。
为了自动化大量副本集的迁移,我们开发了新的软件基础设施。通过简单修改配置文件中的一行内容,我们即可将多个副本集分组,并统一迁移到下一阶段。对于出现问题的副本集,我们可以单独回滚。
在 8.0 的迁移过程中,我们决定统一采用基于行的复制(RBR)。部分 8.0 功能要求使用 RBR,同时这也简化了我们 MyRocks 的移植工作。尽管我们的大部分 MySQL 副本集已经在使用 RBR,但仍有一些副本集依然采用基于语句的复制(SBR),这些副本集通常包含没有高基数键的表。完全切换到 RBR 一直是我们的目标,但为每张表添加主键的工作优先级通常低于其他项目。
因此,我们将 RBR 设为 8.0 的必备条件。经过评估并为每张表添加主键后,我们在今年完成了最后一个 SBR 副本集的切换。RBR 的使用还为我们解决了一些应用问题提供了替代方案,例如部分副本集在迁移到 8.0 主实例时遇到的应用兼容性问题,这将在后续章节中详细讨论。
在 8.0 的迁移过程中,大部分工作集中于通过我们的自动化基础设施和应用程序查询来测试和验证 mysqld 服务器。
随着 MySQL 集群规模的扩大,我们管理服务器的自动化基础设施也在不断扩展。为确保所有 MySQL 自动化流程与 8.0 版本兼容,我们投入资源构建了一个测试环境,该环境使用虚拟机上的测试复制集来验证行为。我们开发了集成测试,以检查每个自动化组件是否能够同时在 5.6 和 8.0 上正确运行。在此过程中,我们发现并修复了多个漏洞和行为差异。
在验证 8.0 服务器的自动化基础设施时,我们遇到了以下问题并进行了相应的解决或调整:
我们希望迁移对应用程序尽可能透明,但某些应用程序查询在 8.0 上遇到了性能回退或错误。
在 MyRocks 迁移过程中,我们构建了一个 MySQL 影子测试框架,用于捕获生产流量并将其重放到测试实例上。对于每个应用工作负载,我们在 8.0 上搭建了测试实例,并将影子流量查询重放到这些实例中。我们捕获并记录了 8.0 服务器返回的错误,并发现了一些有趣的问题。不幸的是,并非所有问题都能在测试中被发现。例如,事务死锁问题是在迁移过程中由应用程序发现的。我们临时将这些应用程序回滚到 5.6,同时研究解决方案。
在对 8.0 服务器进行查询和性能测试时,我们发现了一些需要紧急解决的问题:
相比于 5.6,8.0 的内存使用量显著增加,尤其是在运行 MyRocks 的实例上,因为 8.0 要求必须加载 InnoDB。此外,默认启用的 performance_schema 设置打开了所有监控工具,占用了大量内存。我们通过仅启用少量必要工具,并对代码进行修改以禁用无法手动关闭的表,来限制 memory_schema 的内存使用。然而,并非所有内存增加都由 performance_schema 导致。我们需要检查并优化 InnoDB 的内部数据结构,以进一步减少内存占用。这些调整最终将 8.0 的内存使用控制在了可以接受的范围内。
8.0 的迁移已经进行了数年时间。我们已将许多 InnoDB 复制集完全迁移至 8.0,剩余的复制集则处于迁移路径的不同阶段。随着我们的大部分自定义功能已移植到 8.0,更新至 Oracle 发布的小版本变得更加轻松,我们计划持续跟随最新版本的步伐。
跳过像 5.7 这样的大版本升级引发了一些问题,这些问题在我们的迁移过程中需要解决。
首先,我们无法直接在现有服务器上进行升级,只能通过逻辑导出和恢复来创建新服务器。然而,对于非常大的 mysqld 实例,这个过程可能需要数天时间才能完成,并且由于运行在生产服务器上的迁移过程十分脆弱,常常在完成之前被中断。针对这些大规模实例,我们不得不修改备份和恢复系统以支持重建。
其次,由于没有经历 5.7 阶段,我们难以提前发现 API 的更改。5.7 原本可以通过弃用警告帮助我们修复潜在问题,而我们不得不运行更多影子测试来预先捕获可能的故障。采用能自动转义模式对象名称的 mysql 客户端软件,有助于减少兼容性问题。
同时,在一个复制集中同时支持两个主要版本十分困难。一旦复制集的主实例被提升为 8.0,我们建议尽快禁用并移除 5.6 实例。因为应用用户可能会开始使用仅由 8.0 支持的新功能(如 utf8mb4_0900 排序规则),从而导致 8.0 和 5.6 实例之间的复制流断开。
尽管迁移充满挑战,我们已经看到了运行 8.0 带来的显著好处。一些应用程序甚至选择提前迁移至 8.0,以利用新版本的功能,例如 Document Store 和改进的日期时间支持。同时,我们也在探索如何支持 MyRocks 的存储引擎功能(如即时 DDL)。总体而言,新版本大大扩展了我们在 Facebook 使用 MySQL 的能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。