GitHub数据库架构优化经验:MySql集群虚拟分区和零停机数据迁移
yuyutoo 2024-11-08 15:40 1 浏览 0 评论
熟悉Git线上仓库平台的同学都应该知道Githb和GitLab的基础机构都是一样,开始的时候都是以Ruby on Rails 为应用服务架构,以Mysql数据库为数据库架构。Gitlab经过版本迭代在Gitlab 9的时候数据库换成了PostgreSQL。Github由于是闭源只提供在线服务所以其架构演变我们对其知之甚少。最近GitHub随机数博客公布其数据库架构发展演变情况,请和虫虫一起来学习一下。
概述
GitHub于2007年开发,其初始公司叫Logical Awesome,三位创始人用Ruby on Rails共同开发。2008年2月beta版本上线,4月正式发布,7月发布代码片段收藏的Gists功能,12月发布网站托管的pages功能。2009年issues功能上线,基本功能完备。
其初始的数据架构为Mysql单实例,用来保存其底层元数据。多年来,其数据架构经历了多次迭代,用以支持不断增长的用户规模发展和功能需求。比如某些对某些功能数据进行横向分库,也通过主从副本以,用多个数据库分开来做负载均衡,并通过ProxySQL中间件做统一代理。
GitHub的核心数据架构为一个主要的数据库集群(称为 mysql1),其中包含GitHub核心功能服务的大部分数据,例如用户配置文件、存储库、问题和拉取请求等元数据。
2019年,为了应对面临的增长和可用性挑战,GitHub制定了一个数据架构优化计划,以改进其数据架构和关系数据库分区能力。
截止目前,该优化计划取得显著效果,其mysql1数据集群主机的负载减少了50%,极大地减少了与数据库相关的事件数量并提高了GitHub用户业务的可靠性。
虚拟分区
Github数据架构改进的第一个步是引了数据库模式的虚拟分区的概念。在物理移动数据库表之前,必须确保它们分离虚拟在应用程序层中 ,并且这必须在不影响开发新功能或现有功能的团队的情况下进行。为此,首先将统一在一起的数据库表分组到schema域中,并使用SQL linter强制执行域之间的边界。这样就可以在后续工作中可以安全地对数据进行分区,而不会以跨越分区的查询事务。
Schema域
schema域是用于实现虚拟分区的工具。Schema域描述了一组紧密耦合的数据库表,这些表在查询(例如,在使用表连接或子查询时)和事务中经常一起使用。 例如,gists域包含所有支持GitHub Gist功能的表——比如gists, gist_comments和starred_gists表。这些表是同属的,就应该一直在一起。Schema域是对其进行编码的第一步。
schema域设置了明确的边界,并暴露了功能之间有时隐藏的依赖关系。在Rails应用程序中,信息存储在一个简单的YAML配置文件中
db/schema-domains.yml. 其一个示例如下:
gists:
- gist_comments
- gists
- starred_gists
repositories:
- issues
- pull_requests
- repositories
users:
- avatars
- gpg_keys
- public_keys
- users
linter确保此文件中的表列表与我们的数据库模式相匹配。反过来,同一个 linter强制将schema域分配给每个表。
SQL linter
建立在schema域之上,两个新的SQL linter强制域之间的虚拟边界。他们通过添加查询注释并将它们视为豁免来识别跨越schema域的任何违规查询和事务。 如果一个域没有违规,它就被虚拟分区并准备好物理移动到另一个数据库集群。
查询 linter
查询linter 验证在同一数据库查询中只能引用属于同一schema域的表。如果它检测到来自不同域的表,它会抛出一个异常,并为开发人员提供一条有用的消息提示,以避免该问题。
由于linter仅在开发和测试环境中启用,因此开发人员在开发过程的早期就会遇到违规错误。此外,在CI运行期间,linter确保不会意外引入新的违规行为。
linter 有一种方法可以通过使用特殊注释SQL查询来抑制异常:
/* cross-schema-domain-query-exempted */
为了更加轻松翻边的添加注释,还构建了一个ActiveRecord方法, 以便更轻松:
Repository.joins(:owner).annotate("cross-schema-domain-query-exempted")
# => SELECT * FROM `repositories` INNER JOIN `users` ON `users`.`id` = `repositories.owner_id` /* cross-schema-domain-query-exempted */
通过注释所有导致失败的查询,可以构建需要修改的查询积压。常用来消除豁免的几种方法有:
有时,可以通过触发单独的查询而不是连接表来轻松解决豁免问题。 一个例子是使用 ActiveRecord的 preload方法而不是 includes.
另一个挑战是has_many:through导致的关系JOINs跨来自不同schema域的表。 为此,开发了一个通用的解决方案,has_many有一个 disable_joins告诉 Active Record 不要做任何事情的选项JOIN跨基础表的查询。相反,它会运行多个传递主键值的查询。
在应用程序中而不是在数据库中加入数据是另一种常见的解决方案。例如,替换 INNER JOIN带有两个单独查询的语句,而是在Ruby中执行“联合”操作(例如, A.pluck(:b_id) & B.where(id: ...))。
在某些情况下,这会带来惊人的性能 提升。根据数据结构和规模,MySQL 的查询计划器有时会创建次优的查询执行计划,而应用程序端连接则具有更稳定的性能成本。
与几乎所有与可靠性和性能相关的更改一样,将它们发布在这些 Scientist 实验之后,为请求的子集执行旧的和新的实现,使能够评估每个更改对性能的影响。
事务linter
除了查询,事务也是一个问题。现有的应用程序代码在编写时考虑了特定的数据库模式。MySQL事务保证数据库内表之间的一致性。如果事务包括对将移动到单独数据库的表的查询,它将不再能够保证一致性。
为了了解需要审查的事务,还引入了事务linter。与查询linter类似,它验证在给定事务中一起使用的所有表都属于同一schem域。
该linter在生产中运行并进行大量采样,以将性能影响降至最低。收集和分析 linting结果以了解大多数跨域事务发生的位置,使得可以决定更新某些代码路径或调整我们的数据模型。
在事务一致性保证至关重要的情况下,将数据提取到属于同一schema域的新表中。这确保它们保持在同一个数据库集群上,因此继续具有事务一致性。这通常发生在包含多态表来自不同schema域的数据的中(例如,一个reactions表存储不同功能的记录,如问题、拉取请求、讨论等)
零停机数据迁移
虚拟隔离schema域已准备好物理移动到另一个数据库集群。为了动态移动表,方案中使用了两种不同的方法:Vitess和自定义write-cutover过程。
Vitess
Vitess是云原生的MySQL代理中间件,可以用于对MySql集群进行分片和负载均衡,通过其垂直分片功能可以将生产中的多组表迁移到一起,而无需停机。
VTGate可以实时获取Vitess设置的当前状态,并通过另一个Vitess 组件VTTablet与MySQL实例对话。Vitess的表移动功能则由VReplication提供支持,负责数据库集群之间复制数据。
通过在K8S集群中部署Vitess VTGate,然后应用程序的数据池连接到VTGate进程,而无需直连接MySQL。Vitess MySQ协议,对后端应用是来说等同Mysq实例。
write-cutover过程
由于Vitess的采用在2020年初仍处于探索阶段,因此先开发了一种替代方法来一次性移动大量表格。这降低了依赖单一解决方案来确保GitHub持续可用的风险。
通过使用MySQL的常规复制功能将数据提供给另一个集群。最初,新集群被添加到旧集群的复制树中。然后脚本会快速执行一系列更改以实现切换。
在运行脚本之前,先准备应用程序和数据库复制,同一个复制的集群cluster_b是做为现有集群cluster_a的子集群. 用ProxySQL将客户端连接多路复用连接到 MySQL主数据库。cluster_b的ProxySQL配置为将流量路由到主cluster_a集群。ProxySQL使得能够快速更改数据库流量路由,并且对数据库客户端的影响最小。
通过这种设置,可以将数据库连接移动到cluster_b而无需改动任何东西。所有读取流量仍然流向从cluster_a基本的。所有写入流量都保留在cluster_a。
然后运行一个执行以下内容的转换脚本:
启用只读模式 cluster_a基本的。此时,所有写入cluster_a和cluster_b被阻止。所有尝试写入这些数据库主数据库的Web请求都失败并导致500报错。
从主集群cluster_a查看上次执行的MySQL GTID。
查询luster_b验证最后执行的GTID是否同步。
停止从cluster_a到cluster_b的主从复制。
更新 ProxySQL 路由配置将流量引导至cluster_b。
解除cluster_a和 cluster_b的只读模式。
经过充分的准备和练习,对于最繁忙的数据库表,通过这六个步骤可以在几十毫秒内实现数据切换。由于在一天中流量最低的时间执行此类转换,因此只会由于写入失败而导致少数面向用户的错误。该方法的结果比预期的要好。
经验教训
数据迁移过程中主要使用了write-cutover过程用于拆分mysql1集群。一次迁移了130个最繁忙的表及其支持GitHub的核心功能:存储库、问题和拉取请求。这个过程是作为一种风险缓解策略而创建的。
由于部署拓扑和读写支持等因素,实际中优化方案中并没有选择Vitess作为在每种情况下移动数据库表的工具。预计将来有机会将其用于大多数数据迁移。
结果
通过使用虚拟分区和多个inter检查器,GitHub数据库被划分成不同的Schema域实现不同业务的数据库横向分库。通过Vitess和自定义write-cutover过程实现具体分库的数据迁移,同时实现了数据架构的升级,升级后数据查询QPS由之前的95w/s到现在的120w/s,集群主机的平均负载也减少一半,性能上获得极大的提高,用户的响应和体验都得到极大改善。其数据架构升级过程和所用的工具也都是开源工具(比如ProxySQL,Vitess等),可供大家直接拿来直接使用。
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)