Dimitri Fontaine:利用逻辑复制实现 PostgreSQL 零停机升级

Dimitri Fontaine:利用逻辑复制实现 PostgreSQL 零停机升级

💡 原文英文,约8500词,阅读约需31分钟。
📝

内容提要

本文介绍用逻辑复制实现PostgreSQL大版本零停机升级。相比pg_upgrade需停机和pg_dump恢复耗时随数据量增长,逻辑复制通过新库订阅加反向订阅,将停机压缩至约181毫秒写入停顿,且可毫秒级回滚。需处理序列、物化视图、大对象不复制及DDL问题,并借助标记行判断追平。

🔎

延伸解读

逻辑复制升级的适用边界

逻辑复制升级并非万能。文章指出,它依赖Postgres 10及以上版本作为发布端,且不复制序列、物化视图内容和大对象,DDL也不会自动同步。若源库版本低于10,需借助pglogical或pgcopydb的test_decoding插件。此外,若停机窗口可接受,pg_upgrade --link在秒级完成,反而更简单。因此,选择前需评估版本、数据对象类型和停机预算。

回滚机制与序列拆分策略

反向订阅让回滚可在毫秒级完成,但前提是序列值正确同步。文章建议在首次切换前将序列按奇偶拆分,使新旧库各自生成不冲突的ID。这样即使冻结不彻底,也不会因序列重叠导致主键冲突。代价是ID不再连续,且拆分是长期承诺。对于回滚窗口以分钟计的场景,这一权衡值得。

连接池在切换中的关键作用

使用pgbouncer的PAUSE/RESUME可将切换停机压缩至约142毫秒,且零失败尝试。PAUSE会等待进行中的事务完成,避免只读冻结导致的立即失败。但PAUSE可能被长事务阻塞,需设置idle_in_transaction_session_timeout等超时。应用必须能处理连接中断和重试,否则再快的切换也无意义。

❓

Q&A

用逻辑复制做 PostgreSQL 大版本升级,停机时间能压到多短?

实测写入停顿约 181 毫秒,从冻结旧库到应用切到新库耗时 138 毫秒,期间有 4 次失败尝试,但没有已确认的写入丢失。

逻辑复制升级时,序列、物化视图、大对象这些会一起复制过去吗?

不会。序列的 last_value 在新库为 NULL,首次插入会因主键冲突失败;物化视图存在但未填充;大对象不复制,只有指向它的 oid 列会复制,导致“large object does not exist”错误。生成列会被订阅端重新计算,这是正确的。

逻辑复制升级过程中,旧库上执行 DDL 会怎样?

旧库不会察觉,但新库的 apply worker 会停止并报错,例如“target relation ... is missing replicated column”。新建的表在 alter subscription ... refresh publication 之前不会被订阅感知。建议用独立角色执行 DDL,并用 preventddl 事件触发器或 pg_partman 的 premake 提前创建分区来避免窗口内 DDL。

怎么判断新库已经追平旧库,可以切换了?

用标记行:冻结旧库后,在旧库插入一行到 cutover_markers,然后在新库轮询直到该行出现。直接比较 LSN 或使用 WAIT FOR LSN 都不行,因为冻结本身的提交不会被复制,且 latest_end_lsn 是接收时更新而非应用时更新。

逻辑复制升级后,如果新库有问题,怎么快速回滚?

提前在旧库上创建反向订阅(copy_data = false, origin = none),并让正向订阅也设置 origin = none 以避免循环。回滚时执行与切换相同的四步:冻结新库、等待标记行出现在旧库、将序列值复制回旧库、将应用切回旧库。实测回滚写入停顿 179 毫秒。

逻辑复制升级时,序列值怎么处理?

切换时从旧库的 pg_sequences 生成 setval 语句并在新库执行,将序列值同步过去。另一种方案是拆分序列:旧库保留偶数,新库保留奇数,通过 alter sequence ... increment by 2 restart with ... 实现,避免因冻结不彻底导致主键冲突,但代价是 ID 不再连续。

用 pgbouncer 做切换有什么好处?

应用连接字符串不变,切换在 pgbouncer 后端进行。使用 PAUSE 排空进行中的事务,编辑 databases.ini 并 RELOAD,再 RESUME。实测写入停顿 142 毫秒,零失败尝试,客户端 TCP 连接不关闭。需要事务或语句模式,并注意设置超时避免长事务阻塞 PAUSE。

pg_upgrade --link 和逻辑复制升级,分别适合什么场景?

pg_upgrade --link 需要同一文件系统,且仍需完全停机(读写都停),但速度极快,时间取决于对象数量而非数据量。逻辑复制升级无需停机,写入停顿仅毫秒级,且支持跨硬件、跨区域,但需要处理序列、物化视图等缺口。选择取决于是否可接受停机窗口以及是否更换硬件。

🏷️

标签

➡️

继续阅读