GitHub MySQL 升级之旅

前一段时间,GitHub将内部1200多个MySQL实例组成的数据库集群组(fleet)升级到了 MySQL 8.0 版本,并且发布了一篇博客介绍了大体升级过程,但是很多中文版是机翻的,读起来比较晦涩,所以这几天抽空翻译的一遍,并且对其中的一些概念加了说明。

原文: https://github.blog/2023-12-07-upgrading-github-com-to-mysql-8-0/



  • 🚀 升级的动机

  • 🌐 GitHub MySQL基础设施

  • 🧳 升级旅程准备

    • ❇️ 为升级准备基础设施

    • ❇️ 确保应用程序兼容性

    • ❇️ 沟通和透明度

  • 📈 升级计划

    • ❇️ 步骤1:滚动复制升级

    • ❇️ 步骤2:更新复制拓扑

    • ❇️ 步骤3:将MySQL 8.0节点提升为主节点

    • ❇️ 步骤4:内部面向实例类型的升级

    • ❇️ 步骤5:清理

  • ⏪ 回滚能力

  • 🤔 挑战

  • 🔍那么Vitess呢?

  • 🕒 复制延迟

  • 🚫 查询在CI中通过但在生产环境中失败

  • 📚 学习和收获

  • 🎓 结论


从一个 Ruby on Rails 构建的应用程序开始(当时只用到了一个 MySQL 实例),GitHub便开启了自己的生命之旅,至今已经超过15年了。从那时起,GitHub 为了满足平台的扩展性和弹性需求,不断地演变其 MySQL 的架构,包括建立高可用性、实现自动化测试以及增加数据分区等功能。时至今日,MySQL 依然是 GitHub 基础设施的核心部分,关系型数据库的首要选择。

下面是我们如何将 1200 多个 MySQL 主机升级到 8.0 版本的整个过程。

在不影响服务水平目标(SLO)的前提下,将整个fleet(更广泛的系统集合,规模性远大于集群,可以理解为数据库集群群组,机群)升级到目标版本绝非易事 – 计划、测试以及升级本身耗时就要超过一年,并且还需要 GitHub 内部多个团队之间的通力协作。

🚀 升级的动机

我们为什么需要将 MySQL 升级到 MySQL 8.0呢?

  • 随着 MySQL 5.7 接近其生命周期末期(2023年10月21日起停止维护),我们需要把 fleet 升级到下一个主要版本 – MySQL 8.0。

  • 我们还希望使用能够获得最新安全补丁、错误修复和性能增强的MySQL版本。在 8.0 版本中还有一些我们想要测试并从中受益的新功能,包括在线表结构变更(online DDL)、隐藏索引(invisible indexes)和二进制日志压缩(binary logs compression)等。

🌐 GitHub MySQL基础设施

在我们深入了解升级过程之前,我们先来看看 GitHub 内部 MySQL基础设施的大致情况:

  • 我们的 fleet 拥有1200多台主机,包括Azure虚拟机和我们数据中心的裸机主机。
  • 我们存储了300TB的数据,通过50多个数据库集群提供每秒550万次的查询。
  • 每个集群都具备有高可用性,采用单个主库外加多个从库的架构。
  • 我们的数据都进行了分区。我们利用水平和垂直分片来扩展MySQL集群。我们有专门存储特定产品领域数据的MySQL集群。对于那些超出单主 MySQL集群能力的大数据领域,我们还使用了支持水平分片的 Vitess 集群。
  • 我们拥有一个由 Percona Toolkitgh-ostorchestratorfreno以及内部自动化工具组成的大型工具生态系统,用于管理整个fleet

所有这些加起来构成了一个多样化且复杂的部署环境,需要同时进行升级,并且需要保持我们的SLOs不受影响。

🧳 升级旅程准备

作为 GitHub 的主要数据存储,我们对可用性设定了高标准。鉴于我们 fleet 的庞大规模以及MySQL基础设施的关键性,我们在考虑升级过程时提出了几项特定的要求:

  • 在升级每个MySQL数据库的过程中,我们必须确保遵守我们的服务水平目标(Service Level Objectives,简称SLOs)和服务水平协议(Service Level Agreements,简称SLAs)。

  • 在我们的测试和验证阶段,我们无法考虑到所有的故障模式。因此,为了保持在服务水平目标(SLO)之内,我们需要能够在不中断服务的情况下回滚到MySQL 5.7的先前版本。


  • 在我们的MySQL fleet中,我们有着非常多样化的工作负载。为了降低风险,我们需要对每个数据库集群进行原子级升级,并围绕其他重大变更安排升级计划。这意味着升级过程将会是一个漫长的过程。因此,我们从一开始就知道,我们需要保持一个混合版本的数据库环境稳定运行。

升级准备工作始于2022年7月,甚至在升级单个生产数据库之前,我们就有好几个里程碑需要达到。

❇️ 为升级准备基础设施

我们需要为 MySQL 8.0 确定合适的默认配置值,并执行基线性能基准测试。由于我们需要运行两个版本的 MySQL,我们的工具和自动化系统需要能够处理混合版本,并且需要识别5.78.0 版本之间的新的、不同的或者已废弃的语法。

❇️ 确保应用程序兼容性

我们将MySQL 8.0集成到了所有使用MySQL的应用程序的持续集成(CI)流程中。为了确保在漫长的升级过程中避免任何功能退化,我们在CI环境里同时运行MySQL 5.7和8.0。这一过程帮助我们发现并修复了多种错误和不兼容性问题,使我们能够及时移除不再支持的配置和功能,并规避使用MySQL 8.0中新增的保留关键字。

为了帮助应用程序开发人员过渡到 MySQL 8.0,我们还在 GitHub Codespaces 中提供了选择预构建的 MySQL 8.0 容器的选项,用于调试。同时,我们也提供了MySQL 8.0的开发集群,用于更多的灰度测试。

❇️ 沟通和透明度

我们使用 GitHub Projects 创建了一个滚动日历,用来内部沟通和跟踪我们的升级计划。我们还创建了问题模板,用于记录开发团队和数据库团队在协调升级过程中需要遵循的检查清单。

GitHub MySQL 升级之旅

📈 升级计划

为了符合我们的可用性标准,我们采用了分阶段的升级策略,允许在整个过程中设置检查点并执行回滚操作。

❇️ 步骤1:滚动复制升级

我们的升级过程始于对一个复制节点(从节点)的升级,并在其处于离线状态时进行监控,以确保基本功能的稳定性。随后,我们让生产流量进入升级到的复制节点,并持续监控查询耗时、系统指标以及应用端指标。我们逐步将 8.0 版本的复制节点全都上线,直到完成整个数据中心的升级,然后再逐个迭代其他数据中心。为了方便回滚,我们依然保留了足够数量的5.7版本的复制节点,但我们在上面禁用了生产流量,开始用8.0版本的复制节点提供所有的读取流量服务。

GitHub MySQL 升级之旅

❇️ 步骤2:更新复制拓扑

一旦所有只读流量都通过 8.0 复制节点提供,我们便按照下面的步骤调整复制拓扑:

  • 配置一个 8.0 主节点角色候选者,直接从当前的5.7 主节点复制数据。
  • 在那个 8.0 复制节点下创建了两个复制链:
    • 一组仅包含5.7版本的复制节点(不提供服务,但是做好了回滚准备)
    • 一组仅包含8.0版本的复制节点(提供服务)。

该拓扑结构在这种状态下只保持很短的一段时间(最多几小时),然后我们就进入了下一步。

GitHub MySQL 升级之旅

❇️ 步骤3:将MySQL 8.0节点提升为主节点

我们选择不直接在主数据库节点上进行升级。相反,我们通过使用Orchestrator进行优雅故障切换来将 MySQL 8.0 复制节点升级为主服务器。此时,复制拓扑结构包括一个8.0主服务器,附加到它的两个复制链路:一个是为了回滚而保留的离线的5.7版本副本集,另一个是为了提供服务的 8.0 版本副本集。

我们还在Orchestrator通过配置将 5.7 版本主机加入黑名单,以防止在发生未计划的故障切换时意外回滚。这样可以防止不经意地回退到5.7版本。

GitHub MySQL 升级之旅

❇️ 步骤4:内部面向实例类型的升级

我们还有用于备份或非生产工作负载的辅助服务器。随后,为了保持一致性,这些服务器也一并进行了升级。

❇️ 步骤5:清理

一旦我们确认集群无需回滚,并成功升级到 8.0 版本之后,我们就移除了 5.7 版本的实例。验证包括至少一个完整的24小时流量周期,从而确保在高峰流量期间不会产生问题。

⏪ 回滚能力

保证我们的升级策略安全的核心部分是保持回滚到先前版本MySQL 5.7的能力。我们确保有足够的 5.7 版本复制节点依旧在线,以提供生产流量负载,如果 8.0 版本复制节点的性能不佳,我们将通过禁用 8.0 复制节点的方式发起回滚动作。对于主节点,为了在不丢失数据或中断服务的情况下进行回滚,我们需要在 8.0 和 5.7 之间维护反向数据复制机制。

MySQL支持从一个版本复制数据到下一个较高版本,但不会显式地支持反向复制。当我们在我们的测试集群上将 8.0 主机升级为主服务器时,我们发现所有 5.7 版本复制节点上的复制都中断了。下面是我们需要克服的一些问题:

  1. 在MySQL 8.0 中,utf8mb4是默认的字符集,并采用了更现代的utf8mb4_0900_ai_ci排序规则作为默认值。而之前的 MySQL 5.7 版本支持utf8mb4_unicode_520_ci排序规则,但不支持最新版本的Unicode utf8mb4_0900_ai_ci排序规则。
  2. MySQL 8.0 引入了用于管理权限的角色(Roles)功能,而这个功能在 MySQL 5.7 中是不存在的。当我们将 MySQL 8.0 实例升级为集群中的主节点时,我们遇到了一些了问题。我们的配置管理工具在扩展某些权限集时包含了角色相关的语句,并执行它们,这导致了 5.7 版本复制节点的下游复制中断。为了解决这个问题,在升级窗口期间,我们通过暂时调整受影响用户的权限来解决了这个问题。

为了解决字符排序规则的不兼容性,我们不得不将默认字符编码设置为 utf8,排序规则改为了utf8_unicode_ci

对于 GitHub.com 的整体应用架构,我们采用的Rails配置保障了字符的排序规则一致性,这也简化了客户端配置与数据库的标准化过程。因此,我们对于能够针对我们至关重要的应用保持高效且稳定的数据备份与复制过程充满信心。

补充说明:

原先的utf8utf8mb3(3个字节)

🤔 挑战

在我们的测试、准备和升级过程中,我们遇到了一些技术上的挑战。

🔍那么Vitess呢?

我们使用Vitess来水平切分关系型数据。

在很大程度上,升级我们的Vitess集群与升级MySQL集群类似。由于我们已经在持续集成(CI)环境中运行Vitess,因此我们能够验证查询语句的兼容性。

在针对分片集群的升级策略中,我们逐个分片进行升级。Vitess的代理层vtgate会展示MySQL的版本,而某些客户端行为依赖于这个版本信息。例如,有一个应用使用了Java客户端,在5.7版本实例上禁用了查询缓存—由于查询缓存在 8.0 中被移除,这在升级后导致了阻塞错误。

因此,一旦某个keyspace的MySQL主机升级完成,我们同时需要调整vtgate的配置,确保其准确展现MySQL 8.0的版本信息。

补充说明:

  1. keyspace 是 Vitess 中的逻辑数据库概念,一个分片场景下对应一个 MySQL 集群。
  2. vtgate 是代理服务器,流量会路由到正确的vttablet上面去。
  3. vttablet 是什么呢?是加在底层MySQL实例前面的代理,每个实例有一个。

🕒 复制延迟

我们使用读复制节点(读副本)来扩展读取的可用性。GitHub.com 需要较低的复制延迟来提供实时数据。

在我们早期的测试中,我们遇到了 MySQL 的一个复制bug,该 bug 在 8.0.28 版本中得到了修复:

复制问题:如果设置了系统变量replica_preserve_commit_order = 1的副本实例在高强度负载下长时间运行,实例可能会耗尽提交顺序序列票据。超出最大值后的不正确行为导致应用程序挂起,以及应用程序工作线程在提交顺序队列上无限期地等待。现在,提交顺序序列票据生成器可以正确地循环使用。感谢 Zhai Weixiang 的贡献。(Bug#32891221,Bug#103636)

我们恰好满足触发此漏洞的所有条件。

我们使用了replica_preserve_commit_order,因为我们用的是基于GTID的复制。我们的许多集群在长时间内都承受着高强度的负载,尤其是我们最关键的集群。我们的大多数集群都是高写入量的。

由于这个漏洞已经在上游被修复,我们只需要确保部署的MySQL版本高于8.0.28。

我们还观察到,在MySQL 8.0中,导致复制延迟的大量写入操作有所加剧。这使得避免大量写入操作变得尤为重要。在GitHub,我们使用 freno来根据复制延迟控制写入工作负载。

🚫 查询在CI中通过但在生产环境中失败

我们知道,在生产环境中首次遇到问题是不可避免的—这也是我们逐步推出升级副本策略的原因。我们遇到了在持续集成(CI)中通过,但在面对真实世界工作负载时在生产环境中失败的查询。最值得注意的是,我们遇到了一个问题,即包含大量WHERE IN子句的查询会导致 MySQL 崩溃。我们有包含成千上万个值的大型WHERE IN查询。在这些情况下,我们需要在继续升级过程之前重写这些查询。查询采样有助于追踪和检测这些问题。在GitHub,我们使用 Solarwinds DPM (VividCortex)(VividCortex),一款作为服务(SaaS)的数据库性能监控工具,以实现查询可观察性。

说明:

solarwinds 在数据库监控领域非常好,但是价格略微昂贵

📚 学习和收获

在进行测试、性能调优以及解决检测出的问题的过程中,整个升级过程耗时超过一年,并且 GitHub 多个团队的工程师参与其中。我们将整个fleet升级到了MySQL 8.0,包括测试集群、支持 GitHub.com 的生产集群、以及支持内部工具的实例。这次升级突出了我们的可观测性平台、测试计划和回滚能力的重要性。我们的测试和逐步实施策略使我们能够在早期阶段即发现潜在问题,从而大幅降低在主要升级过程中遭遇新故障模式的风险。

尽管我们采用了逐步推出策略,但在每一步中我们仍需要回滚的能力,同时需要可观测性来识别何时需要回滚的信号。实现回滚的最具挑战性的方面是可以保证新的8.0主节点向5.7 复制节点的反向复制。我们了解到,在 Trilogy client library中的一致性为我们在连接行为上提供了更多的可预测性,并且我们对来自主要 Rails 单体的连接不会破坏反向复制充满信心。

然而,对于那些有来自不同客户端、不同框架/语言的连接的MySQL集群,我们发现反向复制在几小时内就会中断,这缩短了回滚的可行时间窗口。幸运的是,这种情况很少,我们并没有在需要回滚之前遇到复制中断的情况。但这对我们来说是一个教训,那就是拥有已知且易于理解的客户端连接配置是有益的。它强调了制定指导原则和框架以确保此类配置的一致性的价值。

此前我们为数据分区所做的努力得到了回报—它使我们能够针对不同的数据领域进行更有针对性的升级。这一点非常重要,因为一个失败的查询会阻塞整个集群的升级,而将不同的工作负载进行分区使我们能够逐块升级,减少在过程中遇到未知风险的破坏范围。这种做法的一个权衡之处在于,它导致了我们的MySQL集群规模的增长。

GitHub上一次升级MySQL版本时,我们有五个数据库集群,现在我们有50多个集群。为了成功升级,我们必须投资于可观测性、工具和管理fleet的流程。

🎓 结论

MySQL升级只是我们必须要执行的常规维护任务之一

对于我们来说,对于运行在我们fleet上的任何软件都有一个清晰的升级路径至关重要。作为升级项目的一部分,我们开发了新的流程和操作能力,成功完成了 MySQ L版本的升级。然而,升级过程中仍有太多步骤需要手动进行干预,我们希望在未来减少MySQL升级所需的努力和时间。

随着 GitHub.com 的持续发展,我们预计我们的fleet将继续增长,我们也有进一步对数据进行分区的打算,随着时间的推移这将增加我们的 MySQL 集群数量。为运营任务构建自动化和自愈能力可以帮助我们在未来扩展MySQL操作。我们相信,投资于可靠的fleet管理和自动化将使我们能够扩展GitHub,跟上所需的维护工作,提供更加可预测和弹性的系统。

这个项目中的教训为我们的MySQL自动化奠定了基础,并将为未来更高效且同样谨慎和安全地完成升级铺平了道路。


原文始发于微信公众号(小新数据库):GitHub MySQL 升级之旅

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/207585.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!