(本文阅读预计时间:15分钟)
文章转载自公众号:InfoQ
2020年5月,我们与OnGres合作,对GitLab上的Postgres集群进行版本大更新,从9.6版本升级到11版本。升级全部在维护窗口内运行,没有丝毫差错;更新中所有涉及的内容、计划、测试,以及全流程自动化,全部进行拆包,只为实现一次近乎完美的PostgreSQL升级。
本次版本更新,我们面临的最大难题在于如何利用一个规划完善的pg_upgrade,方便且高效地对整体项目进行重要版本升级。为此,我们需要制定一个回滚计划,以保证12节点集群的6TB数据一致的同时,优化恢复目标时间(RTO)后的容量,为600万用户提供每秒300000次的聚合交易服务。
解决工程难题的最佳方案是按照蓝图和设计文档行事。在创建蓝图的过程中,我们需要定义目标问题,评估最合适的解决方案,并考虑每个解决方案的优缺点。
在此,我们附上为这个项目准备的蓝图。
我们为什么要升级PostgreSQL
我们决定在GitLab 13.0中停止对PostgreSQL 10.0的支持,而PostgreSQL 9.6版本将在2021年11月EOL(项目终止)因此,我们需要采取相应的行动。
以下是PostgreSQL 9.6和11版本之间的主要区别:
表分区支持LIST、RANGE,以及HASH
存储过程支持事务
即时编译(JIT)加快查询表达式的运行速度
并行查询,增加并行化数据定义功能
新版本的PostgreSQL继承了版本10中的“逻辑复制——分发数据的发布/订阅框架”,该功能可以使日后的升级更加顺滑,简化了其他相关流程
基于Quorum的提交(commit),确保事务能在集群中指定节点进行提交
提升了通过分区表进行查询的性能
环境与架构
PostgreSQL集群的基础架构容量由12个服务于OLTP以及异步管道的n1-highmem-96 GCP示例组成,同时还有两个不同规格的BI节点,每个节点都有96个CPU内核以及614GB的RAM。HA集群通过Patroni进行管理和配置,以保证Consul集群及其所有复制体在异步流复制中,使用复制槽和WAL对GCS存储桶进行复制工作时的leader选举一致性。
我们的配置目前使用的是Patroni HA解决方案,它会不断收集集群、leader检测,以及节点可用性的关键信息。该解决方案采用Consul的DNS服务等关键功能来实现,进而更新 PgBouncer端点,确保读写和只读流量使用不同架构。

因为HA的缘故,其中两个复制体不在只读服务器列表池中,而是由Consul DNS支持,服务于API。对GitLab架构的几次改进后,我们得以将项目整体降到7个节点。
此外,我们的整个集群平均每周要处理大约181000个交易每秒,如下图所示,流量会在周一有明显增加,并在周一至周五/六内保持该吞吐量。我们需要让维护影响到尽量少的用户,因此流量数据的统计对于设置合适的维护窗口至关重要。

项目整体在全天中最忙碌的时刻可以到达25000交易每秒。

与此同时,项目处理的交易峰值可以到达每秒30万次交易,GitLab.com能到达每秒6万次连接。
我们的升级需求
在生产环境进行升级前,我们首先确定了一些需求:
PostgreSQL版本11上不能有回归。我们开发了一个自定义基准测试来运行更广泛的回归测试,目标是识别PostgreSQL 11中潜在的查询性能下降。
升级应当针对整体项目,并在维护窗口内完成。
使用pg_upgrade升级,其依赖于物理层面,而非逻辑或者复制。
保留一个9.6版本的集群样本。并非所有节点都需要升级,我们应保留一些9.6版本的节点以备回滚。
升级应全自动化,以降低人类失误的可能性。
全部数据库升级的维护窗口只有30分钟。
升级应留有记录并将其发布。
项目
为使生产升级能顺利运行,我们将项目划分为以下几个阶段:
第一阶段:在封闭环境中开发自动化
开发ansible-playbook,并在staging上备份的PostgreSQL环境中进行测试。 独立环境的使用让我们可以随时停止、启动,或者恢复备份,也让我们专注开发,并得以将环境随时回滚到升级前。 我们使用staging上的备份在环境中进行项目升级,在这个过程中,我们也遇到一些诸如在迁移数据库的过程中如何监视不同程序之类的挑战。
在Chef中集成配置管理,并运行数据库磁盘中的一个快照(可用于还原更新前状态)。 通知用户,本次维护窗口将力争对他们工作的影响降到最低,并在没有数据损失风险的情况下进行安全升级。 在对配置管理进行迭代和集成测试后,我们开始在staging上运行端到端测试。这些测试内容是在内部公开的,所以其他共享这个环境的团队会知道staging在这段时间暂时不可用。
第三阶段:在staging上测试端到端升级
正式运行前对环境的检查。我们有时候会在这一步发现认证的问题,有时候也会做一些能提升测试效率的小调整。 停止GitLab上所有应用和流量,在CloudFlare和HA-proxy上添加维护模式,停止包括数据库、sidekiq、workhorse、WEB-API等一切能访问数据库的应用。 升级集群中六个节点中的三个。与生产中部分场景的策略类似,我们同样准备了回滚方案。 为PostgreSQL的更新运行ansible-playbook。首先是数据库leader节点,之后是一些二级节点。 升级之后:我们在ansible-playbook中运行了一些自动化测试,用以检测复制数据与原数据是否相符。 接下来启动应用程序,让我们的QA团队能运行一些测试。他们在升级后的数据库上运行了本地单元测试,我们对负面结果进行了调查。 测试结束后,我们再次停止程序运行,并将staging集群还原到9.6版本,将升级过后的节点关闭到版本11,最后启动旧版集群。Patroni会promote其中一个节点,启动应用后集群就可以收到流量反馈。我们将Chef的配置恢复到集群9.6版本后重建数据库,留出六个节点为下次测试做准备。
我们总共在staging中运行过7次测试,并通过反馈不断完善程序。
第四阶段:升级进入生产环境
生产环境的步骤与staging中类似,我们计划迁移八个节点,留下四个作为备份。
执行项目前期检查 宣布维护开始 运行ansible-playbook以停止流量和应用 运行ansible-playbook以进行PostgreSQL升级 开始验证测试并恢复流量。我们只运行了必需的测试,才能在短暂的维护窗口内完成所有内容
回滚计划只会在数据库不一致或者QA测试出错时才调用,以下是具体步骤:
停止PostgreSQL 11集群 还原Chef中配置到PostgreSQL 9.6 用9.6版本中的四个节点初始化集群。通过这四个节点,我们可以在流量较低的时候恢复GitLab上的活动。 开始接收流量,借此可以尽量减少停机时间。 使用在维护期间和升级前的磁盘快照恢复其他节点
升级中的所有步骤都在用于运行项目的模板中有详细说明
pg_upgrade运行原理
pg_upgrade让我们可以在不用dump/reload策略,不用更多停机时间的情况下,将PostgreSQL数据文件升级到日后的主要版本。
正如在PostgreSQL官方文档中所写,pg_upgrade工具通过避免执行dump/restore的方法来升级PostgreSQL版本。这里有几点细节需要注意:PostgreSQL的主要版本会添加新功能,这些新功能经常会改变系统表的布局,但内部数据存储格式基本会保持不变。如果某次主要版本升级改变了数据格式,那么就不能继续用pg_upgrade了。因此,我们必须要先验证这些版本之间都有什么变化。
还有一点很重要,任何外部模块都必须兼容二进制,虽然你并不能通过pg_upgrade来检查这点。对GitLab的更新来说,我们在升级前先卸载了postgres_exporter等视图及拓展,以便在升级后重新创建,出于兼容性考虑,还要稍作修改。
在更新之前,必须先安装新版本的二进制文件。新的PostgreSQL二进制文件及拓展文件都装在需要升级的主机中。
pg_upgrade在使用时有很多选项。我们选择在Leader节点上使用pg_upgrade的链接模式,因为维护窗口很短暂,只有两个小时。这种模式可以通过inode硬链接文件,避免了复制6TB文件的麻烦。缺点则是旧数据集群无法回滚到9.6版本。我们保存了9.6版本的副本和GCP快照作为后备计划的回滚路径。因为从头开始重建副本是不可能,所以我们选择使用rsync增量功能来进行升级。pg_upgrade的官方文档也有写:“从主服务器上位于旧数据库集群目录和新数据库集群目录上方的目录中,在每个备用服务器的primary上运行此命令。”
ansible-playbook对于这一步的实现,是通过从leader节点到每一个副本都有一个任务,在新旧数据目录中的父目录中触发rsync命令。
回归测试的基准
任何的迁移或数据库升级都需要在最终的生产升级前进行回归测试。对团队来说,数据库测试在升级过程中是至关重要的一步,根据生产过程中的查询数额来进行性能测试,将结果存到pg_stat_statement表中。这些都是在同一个数据集中运行的,一次是在9.6版本,一次是在11版本的迭代。这一步过程可以在下面这个公共的issue中找到:
工具的准备 创建测试环境 计算容量 使用JMeter工具运行基准测试
最后,根据OnGres在这一基准测试上的工作,GitLab将在未来跟进新的基准测试。
主要生产数据库集群的能力评估 数据库容量及饱和度分析
升级过程:全自动就完事了
在升级项目中,升级团队坚持使用自动化和基础架构及代码工具(IaC)。所有流程必须全部自动化,以减少在维护窗口的人为失误。pg_upgrade所有的运行步骤都可以在这个 GitLab的pg_upgrade的模板issue上找到详细说明。
GitLab.com的环境由Terraform和Chef共同管理,所有的升级自动化都是用Ansible 2.9的playbook和roles编写的,我们用了两个ansible-playbook来完成升级自动化:
一个ansible-playbook控制流量和应用:
将Cloudflare设置为维护状态,不接受流量: 停止HA-proxy 停止访问数据库的中间件:Sidekiq、Workhorse、WEB-API
另一个 ansible-playbook 运行升级过程:
协调所有数据库和连接池的流量 控制Patroni集群和Consul实例 在主节点和次级节点上执行升级 收集升级后的统计数据 使用Chef同步更改,以保持配置管理的完整性 验证集群的完整性和状态 执行GCP快照 (可能的)回滚过程
playbook以交互方式逐个运行所有任务,让程序员得以在任意给定执行点跳过或暂停程序。参与staging测试和迭代的所有团队成员都要过目升级过程中的所有步骤,staging环境让我们通过演习提前找到升级过程中潜在的漏洞。而执行和迭代staging中自动化过程则让我们实现了PostgreSQL 9.6版本至11版本的基本无缺陷升级。
为完成本次的版本升级,GitLab的QA团队将部分测试中发现的问题反馈给我们,这一部分的工作可以在这条issue中找到。
PostgreSQL预升级的步骤
升级工作的第一步是“预升级”,这里涉及到预留给回滚的示例。我们做了相应分析,以确保新的集群可以不丢失吞吐量的情况下,以8个示例为起点,保留4个通过标准Patroni集群同步的9.6版本示例,为后续可能需要的回滚情况准备(共计12个实例)。
在这个阶段,我们还需要停止依赖PostgreSQL的服务,诸如PgBouncer、Chef客户端,以及Patroni服务。
在正式开始更新前,必须要告知Patroni,避免任何虚假leader选举,通过GCP快照(通过对应低级备份API获得)进行一致的备份,并通过运行Chef应用新的设置。
PostgreSQL升级阶段
首先,停止所有节点。
然后,运行以下检查:
pg_upgrade版本检查 验证所有节点都已同步,并且不再接受任何流量
一旦主节点数据升级完毕,就会触发rsync进程以同步所有副本数据。在升级完成后,启动Patroni服务,这样所有副本都能轻松更新至新集群的配置。
通过Chef安装二进制文件,新集群在版本方面的设置是在同一个MR中定义的,MR源自GitLab.com,可以安装用于数据库中的拓展项。
最后一个阶段则包括恢复流量、运行初始的真空期,以及最后的启动PgBouncer和Chef客户端服务。
迁移日
到了最后,我们为运行生产线上升级做好了万全准备,团队在周日一早8:45 UTC开始会议(对有的人来说是晚上)。服务将最多下线两小时,当最终的通知下达后,工程团队终于可以开始进行。
升级过程由停止所有流量及相关服务开始,这是为了避免用户在更新中途访问网站。
下面图表显示在服务更新之前,维护期间(图标中的空白部分)、以及维护结束、流量恢复后的流量和HTTP数据统计。

整个流程共花费四个小时,其中仅包括两小时断线时间。
此外,我们录下了PostgreSQL更新的全过程并发布在GitLab Unfiltered上。
PostgreSQL与Oracle:成本、易用性和功能上的差异
点击此处阅读原文
↓↓↓