Facebook 的 MySQL 8.0 迁移之路

本文由Herman LeePradeep Nayak发表在https://engineering.fb.com/,由 Facebook 团队翻译,经 Facebook 授权由 InfoQ 中文站分享。

MySQL 是由 Oracle 开发的开源数据库,为 Facebook 一些最重要的工作负载提供支持。为了满足公司不断发展的业务需求,我们积极开发了 MySQL 的新特性。这些特性改变了 MySQL 的许多方面,包括客户端连接器、存储引擎、优化器和复制。MySQL 每次升级到新的主版本,公司都需要花费大量的时间和精力来迁移工作负载。其中涉及的挑战包括:

 

  • 将定制特性移植到新版本

  • 确保主版本之间的复制兼容性

  • 最大限度减少现有应用程序查询所需的更改

  • 修复性能退化,保障服务器对工作负载的支持

 

我们上次的主版本升级,也就是升级到 MySQL 5.6,花了一年多的时间才完成。但截至 5.7 版发布时,我们仍在根据 5.6 版开发 LSM-Tree 存储引擎 MyRocks。由于同步构建新的存储引擎和升级到 5.7 会大大减缓 MyRocks 的开发进程,我们选择继续使用 5.6 版,直至 MyRocks 开发完成。MySQL 8.0 发布时,我们刚刚把 MyRocks 发布到用户数据库 (UDB) 的服务层级。

新版本拥有一些引人注目的特性,比如基于写集合的并行复制,以及支持原子数据定义语句(DDL)的事务性数据字典。对我们来说,迁移到 8.0 后,我们同样能享受之前错过的 5.7 版本的特性,包括文档存储。5.6 版本即将“寿终正寝”,但我们希望继续活跃在 MySQL 社群中,尤其是因为我们为构建存储引擎 MyRocks 付出了如此巨大的努力。8.0 版本中的增强功能(比如即时 DDL)可以加快 MyRocks 的模式变更,但我们需要迁移到 8.0 代码库才能使用它。考虑到代码更新带来的种种好处,我们最终决定迁移到 8.0 版本。接下来我们将向大家分享,我们是如何处理 8.0 迁移项目的,以及我们在这个过程中发现的一些值得关注的地方。在最初确定项目范围时,我们很清楚,迁移到 8.0 会比迁移到 5.6 或 MyRocks 更加困难。

  • 当时,我们有超过 1,700 个代码补丁要从定制的 5.6 分支版本移植到 8.0。在移植这些变 更时,新的 Facebook MySQL 特性和修复补丁会被源源不断地添加到 5.6 版本的代码 库,使整个过程更加漫长。

  • 我们有许多 MySQL 服务器在生产环境中运行,为大量不同的应用程序服务。我们也有相 当多用来管理 MySQL 实例的软件基础设施。这些应用程序执行着诸如收集统计数据和管 理服务器备份等操作。

  • 从 5.6 升级到 8.0 会完全跳过 5.7 版本。5.6 中使用的一些 API 在 5.7 中会被弃用,在 8.0 中甚至可能会被完全移除,这就要求我们更新所有使用当前已移除 API 的应用程序。

  • Facebook 使用的一些特性与 8.0 中的类似特性不向前兼容,我们必须规划一条弃用和迁 移路径。

  • MyRocks 如果要在 8.0 中运行,必须具备一些增强功能,包括本地分区和崩溃恢复。

 

代码补丁

我们首先开发了 8.0 的分支版本,用于在开发环境中构建和测试。之后,我们便开始了从 5.6 分支版本移植补丁的漫长过程。刚开始时我们有超过 1,700 个补丁需要移植,但我们把它们分成了几大类。我们的大多数自定义代码都有清楚的注释和描述,所以我们很容易就能确定,日后应用程序是否需要它,还是可以直接删除。对于使用特殊关键字或唯一变量名的特性,我们也可以通过搜索应用程序代码库来了解它们的用例,轻松确定相关性。还有一些补丁很是模棱两可,需要我们拿出侦探的精神仔细调查才能了解其历史,比如研究以前的设计文档、帖子或代码评审注释。

我们把所有补丁分成了以下四类:

  1. 删除:8.0 中不再使用或存在同等功能的特性不需要移植。

  2. 构建/客户端:用于支持构建环境、修改 mysqlbinlog 等 MySQL 工具或添加诸如异步客户 端 API 这类功能的非服务器特性会被移植。

  3. 非 MyRocks 服务器:mysqld 服务器中与存储引擎 MyRocks 无关的特性会被移植。

  4. MyRocks 服务器:支持存储引擎 MyRocks 的特性会被移植。

我们使用电子表格来跟踪每个补丁的状态和相关历史信息,对于被删除的补丁,我们记录了删除依据。如果多个补丁更新的是同一特性,我们会把它们打包到一起进行移植。我们为移植和提交到 8.0 分支版本中的补丁添加了 5.6 中的提交信息作为注释。由于需要筛选的补丁非常多,不可避免地会出现移植状态上的不一致,而这些注释对我们解决这些问题非常有帮助。

每一类客户端和服务器相关特性的移植自然而然地成为了软件发布的一个里程碑。具体而言,在完成所有客户端相关更改的移植后,我们就可以将客户端工具和连接器代码更新到 8.0。在完成所有非 MyRocks 服务器特性的移植后,我们就可以为 InnoDB 服务器部署 8.0 mysqld。而在完 成 MyRocks 服务器特性的迁移后,我们就可以更新 MyRocks 的版本。

一些非常复杂的特性要求我们对 8.0 做出重大修改,而且某些方面存在严重的兼容性问题。例 如,上游 8.0 版本中的 binlog 事件格式与我们在定制 5.6 版本时做出的一些修改不兼容。 Facebook 定制 5.6 版本中的特性用到的错误代码与上游 8.0 版本分配给新特性的错误代码存在冲突。最终,我们必须给 5.6 版本的服务器添加补丁,才能使之与 8.0 版本向前兼容。

我们花了几年时间才完成所有这些特性的移植。到最后,我们一共评估了 2300 多个补丁,将其中的 1500 个补丁移植到了 8.0 版本。

迁移之路

我们将多个 mysqld 实例归为一个 MySQL 副本集。副本集中的每个实例都包含相同的数据,但分散部署到了不同地理位置的数据中心,以确保数据可用性和支持故障转移。每个副本集有一个主实例,其余均为辅助实例。主实例负责处理所有写入流量,并将数据异步复制到所有辅助实 例。

 





00:00 / 00:15

    1.0x
    • 2.0x
    • 1.5x
    • 1.25x
    • 1.0x
    • 0.5x

    网页全屏

    全屏

    00:00

    我们从由 5.6 版主实例/5.6 版辅助实例组成的副本集着手,最终目标是获得由 8.0 版主实例/8.0 版辅助实例组成的副本集。整个过程遵循类似于 UDB MyRocks 迁移的计划。

    1. 对于每个副本集,使用 mysqldump 生成逻辑副本,然后通过逻辑副本创建和添加 8.0 版 辅助实例。这些辅助实例目前不会响应应用程序的任何读取流量。

    2. 启用 8.0 版辅助实例的读取功能。

    3. 允许将 8.0 版实例提升为主实例。

    4. 禁用 5.6 版实例的读取功能。

    5. 删除所有 5.6 版实例。

    每个副本集可以独立完成上述各个转换步骤,并且可以根据需要停留在任意阶段。我们将副本集分成了更小的组,引导它们执行每一步转换。如果发现问题,我们可以回滚到上一步。在某些情况下,一些副本集能够在其他副本集尚未开始转换时就到达最后一步。

    由于副本集的数量非常庞大,为了实现自动转换,我们需要构建新的软件基础设施。我们可以把副本集归在一起,这样一来,只需要更改配置文件中的一行,就可以使它们完成各个阶段。任何遇到问题的副本集都可以单独执行回滚操作。

     

    基于行的复制

    在迁移到 8.0 版时,我们决定对“基于行的复制 (RBR)”的使用进行标准化。8.0 版本中的一些特性需要使用 RBR,这简化了我们的 MyRocks 移植工作。虽然我们的大多数 MySQL 副本集已经在使用 RBR,但还有一些副本集仍然在运行基于语句的复制 (SBR),它们的转换并非易事。这类副本集的表通常不包含任何高基数键。虽然完全转换到 RBR 是我们的目标,但添加主键所需的长尾工作的优先级通常低于其他项目。

    因此,我们要求迁移到 8.0 版的实例必须使用 RBR。在评估主键并向每张表添加主键之后,我们在今年完成了最后一个 SBR 副本集的转换。使用 RBR 还为我们提供了一种替代方案,用于解决在将一些副本集移到 8.0 主库时遇到的应用程序问题,这一点稍后我们会谈到。

    自动化验证

    在迁移至 8.0 版本的过程中,很多时候都需要使用自动化基础设施和应用程序查询来测试和验证 mysqld 服务器。

    随着 MySQL 数据量的不断增长,我们用来管理服务器的自动化基础设施的规模也越来越大。为了确保我们的所有 MySQL 自动化都与 8.0 版本兼容,我们投资构建了一个测试环境,通过在虚拟机上运行测试副本集来验证自动化行为。我们编写了一些集成测试,用于检测在 5.6 版本和 8.0 版本上运行的每一项自动化,并验证其准确性。在测试过程中,我们发现了一些漏洞和行为不一致。

    在 8.0 服务器上验证 MySQL 基础架构的各部分时,我们发现并修复(或解决)了若干值得关注 的问题:

    1. 负责解析错误日志文本输出、mysqldump 输出或服务器 show 命令的软件很容易崩溃。 服务器输出的细微变化往往会导致工具的解析逻辑出现漏洞。

    2. 8.0 版本的默认排序规则设置为 utf8mb4,这导致 5.6 实例与 8.0 实例的排序规则不匹 配。8.0 版本中的表可以使用新的 utf8mb4_0900 排序规则,甚至对之前通过 5.6 show create table 命令生成的 create 语句也是如此,因为 5.6 中使用 utf8mb4_general_ci 的数据库模式没有显式指定排序规则。这些表的差异往往会导致复制和模式验证工具出现问 题。

    3. 某些复制失败返回的错误代码发生了变化,我们必须修复自动化程序,以确保正确处理。

    4. 8.0 版本的数据字典弃用了表 .frm 文件,但我们的一些自动化程序会使用此类文件来检测 表模式的更改。

    5. 我们必须更新自动化程序,以便支持 8.0 版本中引入的动态权限配置。

    应用程序验证

    我们希望应用程序的转换尽可能透明,但一些应用程序查询在 8.0 版本中会出现性能下降甚至失败。

     

    对于 MyRocks 迁移,我们构建了一套 MySQL 影子测试框架。此框架会捕获生产流量然后在测试实例中重放。对于每个应用程序工作负载,我们在 8.0 中构建了测试实例,然后在它们之中重放影子流量查询。我们捕获并记录了 8.0 服务器返回的错误,从中发现了一些值得关注的问题。遗憾的是,并非所有这些问题都是在测试期间发现的。例如,事务死锁就是在迁移过程中被应用程序发现的。在研究其他解决方案时,我们将这些应用程序暂时回滚到了 5.6 版本。

    • 8.0 中引入了新的保留关键字,其中一些关键字与应用程序查询中经常使用的表列名和别名相冲突,比如 groups 和 rank。这些查询没有通过反引号对名称进行转义,导致解析错 误。虽然有些应用程序使用会自动在查询中转义列名的软件库,不会遇到这类问题,但并 非所有应用程序都会使用此类软件库。修复这个问题很简单,麻烦的是,追踪应用程序所 有者和生成这些查询的代码库需要时间。

    • 在 5.6 和 8.0 版本之间还发现了一些 REGEXP 不兼容问题。

    • 当执行 insert … on duplicate key 查询时,一些应用程序在 InnoDB 中发生了可重复读事 务死锁。5.6 中有一个漏洞在 8.0 中得到了修复,但此次修复使得死锁更容易发生。在分 析了我们的查询之后,我们选择通过降低隔离级别来解决这些问题。之所以能使用这个方 案,还得益于我们此前已转换为基于行的复制。

    • 我们为 5.6 版定制的文档存储和一些 JSON 函数与 8.0 不兼容。为此,使用文档存储的应用程序需要将文档类型转换为文本,然后才能进行迁移。对于 JSON 函数,我们向 8.0 服务器添加了兼容 5.6 的版本,以便应用程序可以在之后迁移到 8.0 API。

     

    通过对 8.0 服务器开展查询和性能测试,我们发现了一些亟待解决的问题。

    • 我们发现 ACL 缓存方面出现了新的互斥争用热点。当大量连接同时打开时,它们都会阻 塞 ACL 检查。

    • 当存在大量 binlog 文件且 binlog 的高速写入导致文件频繁轮换时,binlog 索引访问也出 现了类似的争用。

    • 多个涉及临时表的查询被中断。这些查询会返回意外错误,或者运行时间太长以致超时。

    内存使用量较 5.6 有所增加,特别是对于我们的 MyRocks 实例,因为在 8.0 中必须加载 InnoDB。默认的 performance_schema 设置启用了所有工具并消耗了大量内存。为了限制内存使用,我们仅保留了少量工具处于启用状态,同时通过修改代码来禁用那些无法手动关闭的表。不过,并非所有多出来的内存使用量都是由 performance_schema 分配的。我们需要检查和修改 各种 InnoDB 内部数据结构,进一步减少内存占用。经此努力,最终我们成功将 8.0 的内存使用量降到了可接受的水平。

    后续计划

    到目前为止,我们的 8.0 迁移之路已经花了几年时间。许多 InnoDB 副本集已经完成转换,能够 完全在 8.0 中运行。剩下的大部分都处于迁移路径的不同阶段。如今,我们的大多数定制特性都已经移植到 8.0,现在再去更新到 Oracle 发布的子版本已经变得相对容易。未来,我们计划跟上最新版本的步伐。

    在迁移时跳过中间的某个主版本(比如我们跳过了 5.7 版)会带来诸多问题,这些都是我们在迁 移过程中需要解决的。

    首先,我们无法就地升级服务器,而是需要利用逻辑转储和恢复来构建新的服务器。然而,考虑 到 mysqld 实例的数据量非常庞大,这可能需要在实时生产服务器上花费许多天的时间,而且这 个过程非常脆弱,在完成之前随时可能会中断。对于这些大型实例,我们必须修改备份和恢复系统才能完成重建。

    其次,5.7 版可以向应用程序客户端发出弃用警告,以便修复潜在问题,但由于我们跳过此版 本,导致之后检测 API 发生的更改要困难得多。我们必须先运行更多影子测试来查找故障,然后才能迁移生产工作负载。使用能够对模式对象名称进行自动转义的 mysql 客户端软件有助于减少兼容性问题。

    在一个副本集中支持两个主版本非常困难。在副本集将其主实例升级到 8.0 之后,最好尽快禁用和删除之前的 5.6 实例。应用程序用户往往更容易发现 8.0 独有的新特性,比如 utf8mb4_0900 排序规则,但使用这些特性可能会导致 8.0 实例和 5.6 实例之间的复制流中断。

     

    尽管迁移道路上困难重重,但我们已经看到了运行 MySQL 8.0 的种种好处。在此之前,一些应用程序已经选择了提早迁移到 8.0,以便可以使用诸如文档存储和改进的日期时间支持等特性。我们一直在考虑如何在 MyRocks 上支持即时 DDL 等存储引擎特性。总的来说,对 Facebook 而言,迁移到新版本大大扩展了 MySQL 的使用范围。 

    原文链接:

    https://engineering.fb.com/2021/07/22/data-infrastructure/mysql/

    本文文字及图片出自 InfoQ

    你也许感兴趣的:

    发表回复

    您的电子邮箱地址不会被公开。 必填项已用 * 标注