查看原文
其他

解读 Retool 团队升级 4TB PostgreSQL 踩坑

装风的小龙 Bytebase 2022-05-27
最近笔者读了一篇很有意思的文章「How Retool upgraded our 4 TB main application PostgreSQL database」 (https://retool.com/blog/how-we-upgraded-postgresql-database)。该文记录了 Retool 团队从 Postgres 9.6 升级到 Postgres 13 的过程,包括了一小时停止服务的升级维护窗口,及其前后的准备工作和踩坑记录。
Retool 是一家专注于帮助客户搭建团队内部工具的公司,他们通过表格等数据处理 UI 组件和 SQL / API 等数据获取方式,让搭建订单处理等内部工具变得更加容易。笔者从这篇文章中总结出了几个有意思的要点,并加以评论。如果对更多细节感兴趣,请阅读原文。


升级动机


Retool 提供了 SaaS 服务,这次需要升级的 Postgres 9.6 正是作为 SaaS 服务的数据库引擎。Retool 升级数据库的动机是正在使用的 Postgres 9.6 将于 2021 年 11 月 11 日结束生命周期支持。这是一种很常见的数据库升级动机,毕竟大家都想能随时获得重要的安全补丁,如果不跟上社区或者商业支持的数据库主流版本,只会给未来留下更多的技术债务。
除此之外,另一种常见的升级数据库的动机则是需要使用最新版本引进的性能提升特性,降低成本或者提升请求响应速度。
由于数据库是一个服务所有状态的管理者,技术团队通常会仔细评估跨主要版本升级数据库的风险和收益,在生产中采取保守的跨主要版本升级策略。


不要停机


作为一个 SaaS 服务提供商,数据库停止工作意味着客户不能继续使用该产品。Retool 作为一个内部工具搭建平台,必然在客户团队的生产流程中占据核心地位,一旦不可用将直接阻碍客户的工作流程。因此,升级 Postgres 数据库过程中的底线就是尽量不要停止服务,或者将停止服务时间缩到最短。

为了达成这一目标,Retool 团队计划采用基于 Postgres Logical Decoding 的数据迁移方案。该方案的核心是让新的 Postgres 13 目标实例持续同步旧的 Postgres 9.6 实例的数据,在切换时只需要短暂停止 SaaS 服务,等待 Postgres 13 实例同步到最新数据,再配置 SaaS 服务使用 Postgres 13 实例并重新启动即可。考虑到 Retool 一共有 4TB 的数据需要迁移,这个方案能在一小时的停机维护时间窗口内完成升级迁移,完美符合 Retool 团队预期。


乐观冒进


然而事情总是不如设想的美好,我们总是应该在生产环节考虑最差情况的预案。

Retool 团队的迁移演练一切顺利,唯独没有考虑到 4TB 数据量过大的风险。在生产环境执行迁移时,由于所用工具的缺陷,处理不了如此巨大的数据量,阻塞了 Postgres 的 Routine Vacuuming,最终造成了意料之外的服务宕机。

教训:我们一定要用有真实性的数据量和工作负载来做生产环境操作的预演,否则就是掩耳盗铃。


兵分两路


Retool 的数据库中有两个超级大表,分别是 2TB 的审计事件表和几百 GB 的操作记录表,由于数据量过大,短时间内无法被迁移工具同步到新的 Postgres 13 实例。前者因为不影响业务逻辑,相对比较好处理:写一个 Python 脚本,在迁移结束后再异步地迁移数据表到新数据库。后者则比较麻烦,因为这是业务逻辑必须的表,需要时刻保持最新,否则会造成数据丢失。

Retool 团队的做法是:提前若干天迁移大部分操作记录表数据到 Postgres 13 实例,在一小时停机维护时间窗口内再同步剩下的数据。这个方式虽然可行,但也增加了额外的步骤和风险,算是对执行迁移的团队能力的额外考验,实属权宜之策。


铤而走险


大部分数据库系统都提供外键约束功能,用以保证外键连接的多表数据一致性,防止数据不一致现象出现,比如外键指向的数据行不存在,进而简化应用程序的异常处理逻辑。为了提升效率,或者是实现更灵活的迁移策略,很多数据迁移工具都会要求在迁移过程中关闭外键约束,在迁移结束后再重新添加约束,Retool 团队使用的基于 Postgres Logical Decoding 的数据迁移工具 Warp 便有这样的限制要求。比较麻烦的是添加外键约束时,Postgres 默认会扫描全表检查外键约束是否正确,对于 4TB 的数据量来说,这个扫描本身就不可能在一小时的维护窗口内完成。

Retool 团队在此走了一着险棋:冒着数据不一致的风险,在数据迁移的停机维护窗口之后再添加大表的外键约束。这意味着从服务重新启动到外键约束重新加上的这段时间内,可能产生外键不一致的数据,从而需要人工处理甚至停止服务来修复数据。还好数据不一致并没有发生,最终有惊无险。
有意思的是,在迁移完成后添加外键约束的过程中,Retool 团队的工程师无意间发现 Postgres 支持添加外键约束时指定 NOT VALID 参数来跳过已有数据的全表扫描约束检查,同时对新数据会强制检查外键约束,之后再使用 VALIDATE CONSTRAINT 语句来扫描全表完成约束检查即可。由于旧数据原本就有外键约束,所以全表扫描的检查一定会通过,同时服务重启后新增数据也有外键约束保护,这样就不用铤而走险了。


总结


Retool 团队记录的迁移过程给我最大的感受是:即便已经很努力地预演和准备了,最后还是可能会漏洞百出,这更加说明预演和准备在大型项目迁移中的重要性。当然,这一点可能不止适用于软件工程😉。

我们有一个可以上 HN 首页的点子,就差一个前端/全栈 实习生了╮(╯▽╰)╭

远程办公参与开源项目如何协作?

令人头大的代码审核

什么是数据库 Schema Drift


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存