全球最大代码托管平台升级到MySQL 8.0实战
作者:Jiaqi Liu、 Daniel Rogart、Daniel Rogart、 Xin WuXin Wu
一、MySQL使用背景
15年前,GitHub作为一个Ruby on Rails应用启动,只使用了MySQL数据库。从那时起,GitHub进化了其MySQL架构,以满足平台的扩展和弹性需求——包括构建高可用性、实现测试自动化和数据分区。时至今日,MySQL仍然是GitHub基础设施的核心部分,也是GitHub的首选关系数据库。
二、升级耗费1年
在不影响服务水平目标(SLO)的情况下,完成这样大规模的升级可谓壮举!整个计划、测试和升级过程,花费了一年多时间,GitHub的多个团队在此过程中展开了广泛合作。
三、升级到MySQL 8.0的动机
MySQL 5.7即将停止维护,升级到8.0版本,可以获得最新安全补丁、错误修复和性能提升。8.0版本中也有GitHub希望测试和使用的新特性,包括即时DDL、隐形索引、压缩binlog日志等。
四、基础设施介绍
在深入探讨升级过程之前,让我们先从直升飞机上俯瞰一下GitHub的MySQL基础设施:
集群由1200多台主机组成,包括Azure虚拟机和数据中心的裸机。
跨50多个数据库集群存储300多TB数据,每秒处理550万查询。
每个集群都配置了主从复制作为高可用。
数据被分区:通过水平分库分表和垂直拆分来扩展MySQL集群,不同的集群存储不同域的产品数据。大型域的数据库使用了水平拆分的Vitess集群。
使用了大量的数据库运维工具,包括Percona Toolkit、gh-ost、orchestrator、freno以及内部的自动化方案。
上述种种使得GitHub的MySQL部署十分复杂多样,升级过程中需要同时保证业务不被中断。
五、准备工作
作为GitHub的主要数据存储,我们对可用性具有很高的标准。由于集群规模和MySQL基础设施的关键性,我们对升级过程也制定了以下要求:
必须在遵守服务级指标(SLO)和服务级协议(SLA)的前提下升级每个MySQL数据库。
我们无法在测试和验证阶段,覆盖所有的故障模式。为了保持在SLO范围内,我们需要能够回滚到之前的MySQL 5.7版本而不中断服务。
在整个MySQL集群中,我们具有非常多样化的工作负载。为降低风险,我们需要原子化地升级每个数据库集群,并在其他重大变更周期中进行调度。这意味着升级过程会很漫长。因此,我们从一开始就知道我们需要能够持续运行混合版本的环境。
2022年7月,升级的准备工作开始,在升级任何一个生产数据库之前,我们就制定了几个里程碑。
1)准备基础设施升级
我们需要确定MySQL 8.0的默认配置值,并进行一些基准性能测试。由于我们需要同时运行两个MySQL版本,我们的工具和自动化流程需要能够处理混合版本,并意识到5.7和8.0之间的新旧语法差异。
2)确保应用程序兼容性
我们在所有使用MySQL的应用程序的持续集成(CI)中添加了MySQL 8.0。我们在CI中并行运行MySQL 5.7和8.0,以确保在漫长的升级过程中不会出现回归。我们在CI中检测到了各种错误和不兼容,这有助于我们删除任何不支持的配置或功能,并规避任何新的保留关键字。
为了帮助开发人员向MySQL 8.0转型,我们还在GitHub Codespaces中提供了选择MySQL 8.0预构建容器的选项,并提供了MySQL 8.0开发集群进行额外的预发布测试。
3)沟通和透明
我们使用GitHub项目创建了一个滚动日历,以便在内部沟通和跟踪我们的升级时间表。我们创建了问题模板来跟踪应用团队和数据库团队的清单,以协调升级。用于跟踪MySQL 8.0升级进度的项目板。
六、升级计划
为了满足我们的可用性标准,我们制定了一个循序渐进的升级策略,允许在整个过程中设置检查点和回滚。
步骤1:滚动升级从库
我们首先升级一个从库,在其离线的同时进行监控以确保基本功能稳定。之后,我们启用生产流量并继续监控查询延迟、系统指标和应用指标。我们逐步启用8.0从库,直到升级完一个数据中心后再迭代到下一个数据中心。我们保留足够的5.7从库以备回滚,但我们关闭了生产流量,开始通过 MySQL 8.0 服务器为所有读提供服务。
添加图片注释,不超过 140 字(可选)
从库升级策略包括在每个数据中心进行渐进推出。
步骤2:更新复制拓扑
一旦所有只读流量都通过8.0从库提供,我们调整复制拓扑如下:
一个8.0主候选配置为直接复制当前的5.7主。
在那个8.0从库下面创建两条复制链:
仅包含5.7从库的一组(不提供服务,但可在回滚时使用)。
仅包含8.0从库的一组(提供服务)。
拓扑结构仅在这种状态下维持很短的时间(最多几小时)直到我们进行下一步。
添加图片注释,不超过 140 字(可选)
为方便升级,拓扑被更新为有两条复制链。
步骤3: 提升 MySQL 8.0 主机成为主节点
我们选择不直接升级主数据库主机,而是通过 Orchestrator 进行平滑切换,将一个 MySQL 8.0 从库提升为主。此时,复制拓扑由一个 8.0 主组成,其下连接两条复制链:一组离线的 5.7 从库以备回滚,以及一组在线的 8.0 从库。
Orchestrator 也被配置为,在非计划切换时将 5.7 主机列入黑名单,防止意外回滚。
添加图片注释,不超过 140 字(可选)
通过主机切换和其他步骤最终完成数据库升级到 MySQL 8.0。
步骤4:升级内部实例
我们还有一些备份或者非生产负载的辅助服务,这些后续也升级到MySQL 8.0 以保持一致性。
步骤5:清理工作
一旦确认集群不需要回滚且成功升级到 MySQL 8.0,我们将删除MySQL 5.7 服务。验证包括,通过至少一个完整的 24 小时流量周期,来确保高峰时段不会有问题。
七、应急措施
保持回滚到之前 MySQL 5.7 版本的能力是我们升级策略的一个核心部分。对于从库,我们要保留足够的 5.7 从库在线,以服务于生产,如果 8.0 从库性能不佳,则可以通过禁用它们来触发回滚。对于主库,为了不损失数据和不中断服务进行回滚,我们需要能够在 8.0 和 5.7 之间维持向后的数据复制。
MySQL 支持从一个版本复制到下一个更高版本,但不明确支持反向复制(MySQL复制兼容性)。当我们在预发环境中测试提升一个 8.0 主机时,发现所有 5.7 从库的复制都中断了。我们需要解决一些问题:
在 MySQL 8.0 中,utf8mb4 是默认字符集,使用更现代的 utf8mb4_0900_ai_ci 校对规则作为默认值。之前的 5.7 版本支持 utf8mb4_unicode_520_ci 但不支持最新版本的 Unicode utf8mb4_0900_ai_ci。
MySQL 8.0 引入了角色用于管理权限,但这个特性在 5.7 中不存在。当一个 8.0 实例被提升为集群中的主库时,我们遇到了问题。我们的配置管理正在扩展某些权限集合以包含角色语句并执行它们,这中断了 5.7 从库的后向复制。我们通过在升级窗口临时调整受影响用户的权限,来解决此问题。
为了解决字符整理不兼容的问题,我们不得不将默认字符编码设置为utf8,整理规则设置为 utf8_unicode_ci。
对于GitHub.com的单体应用,我们的Rails配置确保了字符校对的一致性,使得将客户端配置标准化到数据库变得更加容易。因此,我们非常有信心能够保持我们最关键应用的向后复制。
八、勇敢面对挑战
在我们的测试、准备和升级过程中,我们遇到了一些技术挑战。
1)分库分表Vitess怎么办?
我们使用Vitess进行关系数据的水平分片。在大多数情况下,升级Vitess集群和升级MySQL集群差不多。我们已经在CI中运行Vitess,所以可以验证查询兼容性。在为分片集群制定的升级策略中,我们一次升级一个分片。Vitess代理层VTgate会广播MySQL的版本,某些客户端行为依赖这个版本信息。例如,一个应用使用了一个在5.7服务器上禁用查询缓存的Java客户端——由于查询缓存在8.0中被移除,这为他们生成了阻塞错误。因此,一旦某个键空间的MySQL主机被升级,我们就必须确保也更新VTgate的设置以通告8.0。
2)复制延迟
我们使用从库来扩展读。GitHub.com 要求复制延迟很低以提供实时数据。
在我们的早期测试中,我们遇到了一个MySQL的复制bug,它在8.0.28版本中被修复:
如果将replica_preserve_commit_order系统变量设置为1的从库在高负载下长时间运行,实例可能会耗尽提交顺序的序列号。超过最大值后不正确的行为会导致应用程序挂起,应用程序工作线程在提交顺序队列上无限期等待。现在,提交顺序序列号生成器可以正确循环。感谢翟伟翔的贡献。(Bug #32891221, Bug #103636)
我们恰好符合触发这个bug的所有条件。
我们使用基于GTID的复制,所以使用了replica_preserve_commit_order。
在我们许多集群中有长时间的高强度负载,这在我们所有最关键的集群都是这样,大多数集群负载很重。
由于这个bug已经在上游打了补丁,我们只需要确保部署高于8.0.28的MySQL版本即可。
我们还观察到,驱动复制延迟的大量写操作在MySQL 8.0中被加剧。这使得我们避免写操作突发变得更加重要。在GitHub,我们使用freno,根据复制延迟,来管控写工作负载。
3)查询在集成环境(CI)中通过,但在生产中失败
我们知道在生产环境中看到问题是不可避免的——这就是我们渐进升级从库策略的原因。我们遇到了在遭遇现实世界工作负载时,CI通过但在生产中失败的查询。最明显的问题是带有大型WHERE IN子句的查询会导致MySQL崩溃,这些查询包含了成千上万的值。在这些情况下,我们需要在继续升级过程之前重写查询。查询取样有助于跟踪和检测这些问题。在GitHub,我们使用Solarwinds DPM(VividCortex)这个数据库性能监控SaaS来观测查询。
九、学到的经验和教训
在测试、性能调优和解决已识别问题的过程中,整个升级过程耗时一年以上,涉及到 GitHub 多个团队的工程师。我们将整个服务集群升级到 MySQL 8.0,包括 GitHub.com 支持的演示服务器群、生产服务器群以及支持内部工具的实例。这次升级凸显了我们可观察性平台、测试计划和回滚能力的重要性。测试和渐进式的发布战略使我们能够早期识别问题,降低主要升级中遇到新故障模式的可能性。
虽然有渐进式的发布战略,但我们仍然需要在每一步有进行回滚的能力,同时我们需要可观察性以识别何时需要回滚的信号。启用回滚的最具挑战性的方面,是保持来自新的 8.0 主服务器到 5.7
复制的向后兼容性。我们发现 Trilogy 客户库的一致性为我们提供了更可预测的连接行为,并使我们能够确信来自主要 Rails 单体的连接不会破坏向后复制。
然而,对于一些 MySQL 群集,其中包含来自不同框架/语言的多个不同客户端的连接,我们看到向后复制在几小时内中断,这缩短了回滚的机会窗口。幸运的是,这些情况很少,我们没有出现在我们需要回滚之前复制中断的情况。但对我们而言,这是一个教训,即在客户端连接配置已知且深谙其中的情况下,有益于获得一致性。这强调了制定指南和框架以确保此类配置一致性的价值。
之前对数据进行分区的努力取得了成果,它使我们能够为不同数据领域实现更有针对性的升级。这对于一个失败的查询可能会阻止整个群集升级的情况至关重要,将不同的工作负载进行分区允许我们逐步升级,减小在过程中遇到的未知风险的影响范围。这其中的权衡是,这也意味着我们的 MySQL 群集规模增加了。
上一次 GitHub 升级 MySQL 版本时,我们有五个数据库群集,现在我们有 50 多个群集。为了成功升级,我们不得不在可观察性、工具和管理群集的过程中投入大量资源。
十、总结与回顾
MySQL升级只是我们必须执行的例行维护之一 - 对于我们运行在服务器群上的任何软件,都很关键能够拥有升级路径。作为升级项目的一部分,我们制定了新的流程和运营能力,成功完成了MySQL版本的升级。然而,在升级过程中,仍然有许多需要手动干预的步骤,我们希望减少完成未来MySQL升级所需的工作量和时间。
我们预计随着GitHub.com的增长,我们的服务器群将继续扩大,我们计划进一步对数据进行分区,这将随着时间的推移增加我们的MySQL群集数量。为运营任务和自愈能力构建自动化可以帮助我们在未来扩展MySQL操作。我们相信投资于可靠的服务器群管理和自动化将使我们能够扩展GitHub并跟上所需的维护工作,提供一个更可预测和具有弹性的系统。
这个项目的经验为我们的MySQL自动化奠定了基础,并将为将来更高效地完成升级铺平道路,但仍保持同样的关注和安全水平。
#PG数据库工程师的摇篮#PostgreSQL考试#PostgreSQL培训#PGCCC
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!