百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

一次近乎完美的PostgreSQL版本大升级实践

xsobi 2024-12-01 05:36 22 浏览

2020 年 5 月,我们与 OnGres 合作,对 GitLab 上的 Postgres 集群进行版本大更新,从 9.6 版本升级到 11 版本。升级全部在维护窗口内运行,没有丝毫差错;更新中所有涉及的内容、计划、测试,以及全流程自动化,全部进行拆包,只为实现一次近乎完美的 PostgreSQL 升级。

本次版本更新,我们面临的最大难题在于如何利用一个规划完善的 pg_upgrade,方便且高效地对整体项目进行重要版本升级。为此,我们需要制定一个回滚计划,以保证 12 节点集群的 6 TB 数据一致的同时,优化恢复目标时间(RTO)后的容量,为 600 万用户提供每秒 300000 次的聚合交易服务。

解决工程难题的最佳方案是按照蓝图和设计文档行事。在创建蓝图的过程中,我们需要定义目标问题,评估最合适的解决方案,并考虑每个解决方案的优缺点。

在此,我们附上为这个项目准备的蓝图链接。

我们为什么要升级PostgreSQL

我们决定在 GitLab 13.0 中停止对 PostgreSQL 10.0 的支持,而 PostgreSQL 9.6 版本将在 2021 年 11 月 EOL(项目终止),因此,我们需要采取相应的行动。

以下是 PostgreSQL9.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 上的备份在环境中进行项目升级,在这个过程中,我们也遇到一些诸如在迁移数据库的过程中如何监视不同程序之类的挑战。

第二阶段:在 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 上。

原文链接:

https://about.gitlab.com/blog/2020/09/11/gitlab-pg-upgrade/

关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书,点击文末「了解更多」,即可移步InfoQ官网,获取最新资讯~

相关推荐

开源鸿蒙OpenHarmony 5.1.0(API 18)正式上线:进一步增强ArkUI

IT之家5月4日消息,开源鸿蒙OpenHarmony是由开放原子开源基金会孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,搭建一个智能终端设备操作系统的框架和平台,促进万物互联...

day57:jQuery事件

链式操作<p>111</p><p>222</p>"""一行代码将第一个p标签变成红色第二个p标签变成绿色""...

基础干货:Pandas-Data Frame基础知识点总结

熊猫数据框架可以从列表、字典和字典列表等中创建。通过从现有存储中加载数据集来创建PandasDataFrame,存储可以是SQL数据库、CSV文件和Excel文件。Dataframe是一种二维数据结构...

万字长文带你通俗理解LightGBM

GBDT(GradientBoostingDecisionTree)是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等...

数组(I) - 网络统计学(11)

数组是将相同类型的数据按照特定顺序组合在一起的集合。其中,每个独立的变量都被称为数组元素,有时也会被称作下标变量。而用于区分各个数组元素的数字编号,则被称为下标。通常各种计算机编程语言都具备一定的数值...

VUE业务中数据绑定无效-this.$set来帮忙

这个方法之前看文档肯定是看到了,但是从来没有遇到问题,就抛之脑后了!这不遇到问题了还得请它来帮忙!车祸现场问题剖析当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,也许这个时...

JS数组Reduce的妙用,收藏等于学会

作者:前端发现者来源:前端发现本文转载自微信公众号「前端发现」,作者前端发现者。转载本文请联系前端发现公众号。说到处理数组的方法,想必大家都不陌生了,今天我们一起来学习下理数组常见场景下的方法。首先来...

Java 异常处理通关指南

前言在理想世界中,程序永远不会出现问题,用户输入的数据永远是正确的,逻辑没有任何问题,选择打开的文件也一定是存在的,内存永远是够用的……!但是现实世界里一旦出现这些问题,如果处理不好,程序就不能正常...

Python 数据类型 - 数组

Python数据类型-数组在本节中,你将学习如何在Python中创建和访问数组的元素。数组是相同数据类型的元素的集合。数组和列表之间的主要区别是列表可以具有不同数据类型的元素。在Pytho...

「Python系列」 Python 数据结构之数组

在大多数编程语言中,数组都是一个基础而又普遍的数据结构。在很多算法中都有广泛的应用。在本文中,我们将了解鲜为人知的Python数组实现,该实现仅使用Python标准库中包含的核心语言功能。我们将看到每...

COUNTIF函数用法大全

COUNTIF函数用于统计满足某个条件的单元格的数量,其基本用法为:COUNTIF(统计区域,指定的条件)如下图所示,要统计A列有几个5,可以使用以下公式:=COUNTIF(A2:A10,5)咱们把C...

强大到没有对手的Aggregate函数,碾压Sum等19个函数,你真的会吗

从前期的学习中,我们已经知道,Subtotal函数的功能非常强大,但还有一个函数,其功能强大到了没有对手,此函数就是Aggregate,可以实现Sum、Average、Count、Max、Min、Pr...

RISC-V并不完美?

本文最初完成于几年之前,彼时作者正在ARM公司担任执行核心验证工程师职位。作者当时的工作深入或围绕多种处理器核心,而文中提到的观点深受这些经验的影响,换句话说,这些观点存在不同程度的偏见。作者依旧...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础2.索引类型2.1哈希索引2.2有序数组2.3B+树索引(InnoDB)3.联合索引4.最左前缀原则5.覆盖索引6.索引下推总结:1.索引基础索引对查询的速度有着至...

是时候学习一下INDEX+MATCH这个超级查询神器了

【温馨提示】亲爱的朋友,阅读之前请您点击【关注】,您的支持将是我最大的动力!当我们在处理表格数据查询时,首先会想到一个超牛查询函数VLOOKUP()。如果你觉得VLOOKUP函数超牛的话,今天阿钟老师...