Retool的云托管产品是由运行在微软Azure云中的一个强大的4TB Postgres数据库支持的,去年秋天,我们把这个数据库从Postgres 9.6版本迁移到13版本,而且停机时间极短。
我们是如何做到的?坦率地说,从A点到B点并不是一条完全笔直的道路。在这篇文章中,我们将讲述这个故事,并分享帮助你进行类似升级的技巧。
对于那些刚接触Retool的人来说,是一个快速构建内部工具的平台。你可以使用一个拖放编辑器来构建UI,并轻松地将它们与你自己的数据源挂钩,包括数据库、API和第三方工具。你可以把Retool作为云托管产品(有我们在这篇文章中谈到的数据库支持),或者你可以自己托管。正如4TB的数据库规模所表明的那样,许多Retool客户正在云中构建许多应用程序。
去年秋天,我们决定升级我们的主要Postgres数据库,有一个令人信服的理由。Postgres 9.6将在2021年11月11日达到寿命终点,这意味着它将不再收到错误修复或安全更新。我们不想让客户的数据有任何风险,所以我们不能继续使用这个版本。事情就是这么简单。
技术设计
这次升级涉及几个高层次的决定。
- 我们应该升级到哪个版本的Postgres?
- 我们使用什么策略来进行升级?
- 我们如何测试升级?
在我们深入研究之前,让我们回顾一下我们的约束和目标。只有几个。
在2021年11月11日之前完成升级。 尽量减少停机时间,特别是在全球周一至周五的工作时间。这是硬性期限之后最重要的考虑因素,因为Retool对我们的许多客户至关重要。
当考虑在4TB上操作时,停机时间尤其是一个因素。在这种规模下,容易的事情变得更难。 我们希望我们的维护窗口最多为一小时左右。
- 在我们不得不再次升级之前,最大限度地增加这次升级为我们赢得的时间。
PostgreSQL 13
我们决定升级到Postgres 13,因为它符合上述所有的标准,特别是最后一个标准:在下一次升级之前为我们赢得最多的时间。
当我们开始准备升级时,Postgres 13是Postgres的最高发布版本,其支持窗口到2025年11月。我们预计在这个支持窗口结束时,我们将把我们的数据库分片,并逐步进行下一次实质性的版本升级。
Postgres 13还提供了一些以前版本所没有的功能。以下是完整的列表,以下是我们最兴奋的几个。
- 主要的性能改进,包括并行查询的执行。
- 能够安全地添加具有非空默认值的列,这消除了一个常见的脚炮。在早期的Postgres版本中,添加默认值为非空的列会导致Postgres执行表的重写,同时阻塞并发的读和写,这可能会导致停机。
- 对索引进行并行的吸尘处理。(Retool有几个写流量大的表,我们非常关心吸尘问题)。
升级战略
很好,我们已经选定了一个目标版本。现在,我们要如何达到这个目标呢?
一般来说,升级Postgres数据库版本的最简单方法是进行pg_dump和pg_restore。你关闭你的应用程序,等待所有的连接终止,然后关闭数据库。在数据库处于冻结状态时,你把它的内容转储到磁盘上,然后把内容恢复到一个运行在目标Postgres版本的新数据库服务器上。一旦恢复完成,你就把你的应用指向新的数据库,然后把应用带回来。
这个升级选项很吸引人,因为它既简单,又能完全保证数据在旧数据库和新数据库之间不会不同步。但我们马上就排除了这个选项,因为我们想尽量减少停机时间—在4TB上进行转储和恢复,需要几天的停机时间,而不是几小时或几分钟。
我们转而采用了基于逻辑复制的策略。通过这种方法,你可以并行地运行两个数据库的副本:你要升级的主数据库,和一个以目标Postgres版本运行的辅助 “跟随者 “数据库。主数据库将其持久性存储的变化(通过解码其写头日志)发布到辅助数据库上,允许辅助数据库快速复制主数据库的状态。这有效地消除了在目标Postgres版本恢复数据库的等待:相反,目标数据库总是最新的。
值得注意的是,这种方法需要的停机时间比 “转储和恢复 “策略少得多。我们不需要重建整个数据库,只需要停止应用程序,等待旧的v9.6主数据库的所有事务完成,等待v13辅助数据库赶上,然后将应用程序指向辅助数据库。而不是几天,这可以在几分钟内完成。
测试策略
我们维护一个云Retool实例的暂存环境。我们的测试策略是在这个暂存环境中进行多次测试运行,并通过这个过程创建和迭代一个详细的运行手册。
测试运行和运行手册对我们很有帮助。正如你在下面的章节中所看到的,我们在维护窗口期间执行了许多手动步骤。在最后的切换过程中,这些步骤基本上没有出现问题,因为我们在之前几周进行了多次彩排,这有助于我们建立一个非常详细的运行手册。
我们最初的主要疏忽是没有用一个有代表性的工作负载来进行测试。暂存数据库比生产数据库小,即使逻辑复制策略应该使我们能够处理更大的生产工作量,但我们错过了一些细节,导致Retool的云服务中断。我们将在下面的章节中概述这些细节,但这是我们希望传达的最大教训:用有代表性的工作负载进行测试的重要性。
实践中的计划:技术细节
实施逻辑复制
我们最终使用了Warp。值得注意的是,Azure的单服务器Postgres产品不支持pglogical Postgres扩展,我们的研究使我们相信这是Postgres版本10之前逻辑复制的最佳支持选项。
我们早期走的一条弯路是尝试使用Azure的数据库迁移服务(DMS)。DMS首先对源数据库进行快照,然后将其恢复到目标数据库服务器。一旦最初的转储和恢复完成,DMS就会开启逻辑解码,这是Postgres的一项功能,将持久的数据库变化流向外部用户。
然而,在我们的4TB生产数据库上,初始转储和恢复从未完成。DMS遇到了一个错误,但没有向我们报告这个错误。同时,尽管没有取得任何进展,但DMS在我们的9.6初级数据库中保留了交易。这些长期运行的事务反过来又阻塞了Postgres的自动真空功能,因为真空进程无法清理长期运行的事务开始后产生的死图元。随着死亡图元的堆积,9.6主系统的性能开始受到影响。这导致了我们上面提到的故障。(我们后来增加了监控,以跟踪Postgres的未抽真空的元组数,使我们能够主动发现危险的情况。)
Warp的功能与DMS类似,但提供了更多的配置选项。特别是,Warp支持并行处理,以加速初始转储和恢复。
我们不得不做一些调整来哄骗Warp来处理我们的数据库。Warp希望所有的表都有一个单列主键,所以我们不得不把复合主键转换成唯一约束,并增加标量主键。除此之外,Warp的使用非常简单。
跳过大表的复制
我们进一步优化了我们的方法,让Warp跳过两个特别大的表,这两个表主导了转储和恢复的运行时间。我们这样做是因为pg_dump不能在单个表上并行操作,所以最大的表将决定最短的迁移时间。
为了处理我们在Warp中跳过的两个大表,我们写了一个Python脚本,将数据从旧的数据库服务器批量转移到新的数据库服务器。较大的2TB的表,即应用程序中的审计事件的纯附录表,很容易转移:我们等到切换后再迁移内容,因为即使该表是空的,Retool产品也能正常工作。我们还选择将非常老的审计事件转移到一个备份存储解决方案,以减少表的大小。
另一个表是一个数百兆字节的仅有附录的日志,记录了所有Retool应用程序的所有编辑,称为page_saves,这个表比较棘手。这张表是所有Retool应用程序的真实来源,所以它需要在我们从维护中回来的时候是最新的。为了解决这个问题,我们在维护窗口前的几天里迁移了大部分内容,并在窗口期间迁移了剩余的内容。虽然这个方法很有效,但我们注意到它确实增加了风险,因为我们现在有更多的工作要在有限的维护窗口内完成。
创建一个运行簿
在维护窗口期间,我们在运行手册中的步骤大致是这样的。
- 停止Retool服务,让所有未完成的数据库事务提交。
- 等待后续的Postgres 13数据库赶上逻辑解码的进度。
- 同时,把剩余的page_saves行复制过来。
- 一旦所有的数据都在Postgres 13的服务器中,启用主键约束的执行。(Warp要求这些约束被禁用)。
- 启用触发器(Warp要求禁用触发器。)
- 重置所有的序列值,这样一旦应用程序重新上线,序列整数主键分配就可以工作。
- 缓慢地带回Retool服务,指向新的数据库而不是旧的,执行健康检查。
启用外键约束的执行
从上面的运行手册可以看出,我们必须做的一个步骤是关闭然后重新启用外键约束检查。复杂的是,默认情况下,Postgres在启用外键约束时,会运行一次全表扫描,以验证所有现有的行是否根据新的约束有效。对于我们的大型数据库来说,这是一个问题:Postgres根本无法在一小时的维护窗口内扫描数千字节的数据。
为了解决这个问题,我们最终选择在几个大表上不强制执行外键约束。我们认为这可能是安全的,因为Retool的产品逻辑会执行自己的一致性检查,而且也不会从引用的表中删除,这意味着我们不太可能留下一个悬空的引用。尽管如此,这也是一种风险;如果我们的推理不正确,我们最终会有一堆无效的数据需要清理。
后来,在维护后的清理工作中,我们恢复了丢失的外键约束,我们发现Postgres为我们的问题提供了一个整洁的解决方案:ALTER TABLE的NOT VALID选项。添加一个带有NOT VALID的约束,使得Postgres对新的数据而不是现有的数据执行约束,从而绕过了昂贵的全表扫描。之后,你只需要运行ALTER TABLE … VALIDATE CONSTRAINT,它将运行全表扫描并从约束中删除 “无效 “标志。当我们这样做的时候,我们发现在我们的表中没有无效的数据,这让我们松了一口气 我们希望我们在维护窗口之前就知道这个选项。
结果
我们把维护窗口安排在10月23日(星期六)晚些时候,在Retool云流量最低的时期。通过上述配置,我们能够在15分钟内建立一个新的13版数据库服务器,通过逻辑解码订阅我们9.6版主服务器的变化。
最后,在Warp的帮助下,通过逻辑复制策略,以及在测试环境中的彩排,我们建立了一个强大的运行手册,使我们能够将4TB的数据库从Postgres 9.6迁移到13。在这个过程中,我们认识到了在真实的工作负载上进行测试的重要性,创造性地使用了跳过大型的、不那么关键的表,并且了解到(有点晚)Postgres允许你有选择地在新数据上执行外键约束,而不是所有数据。我们希望你也能从我们的经验中学到一些东西。
文章来源:https://retool.com/blog/how-we-upgraded-postgresql-database/