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