在容器内使用 pg_upgrade 快速升级 PostgreSQL 大本版
阅读时间 1 分钟
我计划将 PostgreSQL 从 15 版本升级到 17 版本。跨越大本版升级,需要手动干预。官方提供了一个名为 pg_upgrade
的工具,可以实现集群的原位升级,无需从旧集群导出数据再恢复到新集群。
但 pg_upgrade
的 官方文档 描述的是通过 tar
或 make install
等方式安装的 PostgreSQL 升级流程。网上许多教程都声称如果使用 Docker 就必须导出再导入所有数据。
我不认同这种说法,决定尝试在 Docker 容器内实现原位升级。另一个重要原因是我们的业务要求数据库停机时间最短。
根据文档,pg_upgrade
支持克隆 (Clone) 或链接 (Link) 模式,能大幅提升升级速度。以我的测试为例,300GB 的数据库升级耗时不到 10 秒。
链接模式和克隆都需要文件系统支持特定功能。我测试过 ZFS
和 BTRFS
,基本上所有支持写时复制 (CoW) 的文件系统都适用。
技术难点
使用 pg_upgrade
需要同时存在新旧版本的二进制文件和支持文件。而在 Docker 环境中,容器内通常只包含单一版本的文件。
解决方案很简单:从 15 版本容器中复制出二进制文件,然后挂载到 17 版本容器的对应目录,最后在 17 版本容器中执行升级。
备份阶段
首先使用 pg_dumpall
进行全量备份,并用 zstd
压缩:
docker exec -it postgresql pg_dumpall -U postgres | zstd > /mnt/backup.sql.zst
准备工作
复制旧版本的必要文件:
cd /srv/postgresql
docker cp postgresql_postgresql_1:/usr/lib/postgresql/15 ./usr-lib-15
docker cp postgresql_postgresql_1:/usr/share/postgresql/15 ./usr-share-15
此时工作目录包含:
data
:旧版数据文件docker-compose.yaml
:旧版容器的编排文件usr-lib-15
:旧版二进制文件usr-share-15
:旧版支持文件
创建新数据库
docker run -it --rm --name pg17 \
-v ./data-17:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=YourPassword \
docker.io/pgvector/pgvector:pg17
在当前目录下创新的数据库创建完成之后可以按下 Ctrl
+ C
停止。新的数据库创建在 ./data-17
文件夹里。
执行升级
docker run -it --rm --name pg17 \
-v ./data-17:/var/lib/postgresql/data \
-v ./data:/data \
-v ./usr-lib-15:/usr/lib/postgresql/15 \
-v ./usr-share-15:/usr/share/postgresql/15 \
--user 999 \
--workdir /var/lib/postgresql/data \
-e POSTGRES_PASSWORD=YourPostgreSQLPsssword \
docker.io/pgvector/pgvector:pg17 \
pg_upgrade \
--old-datadir /data \
--new-datadir /var/lib/postgresql/data \
--old-bindir /usr/lib/postgresql/15/bin \
--new-bindir /usr/lib/postgresql/17/bin \
--clone
各个参数说明:
-v ./data-17:/var/lib/postgresql/data
挂载新数据目录 gg-v ./data:/data
挂载旧数据目录-v ./usr-lib-15:/usr/lib/postgresql/15
挂载旧版二进制文件-v ./usr-share-15:/usr/share/postgresql/15
挂载旧版支持文件--user 999 --workdir /var/lib/postgresql/data
确保 pg_upgrade 有写入权限-e POSTGRES_PASSWORD
设置密码(若非默认 postgres 用户需额外配置)--clone
原地执行数据升级操作
如果在使用 pg_upgrade
进行 PostgreSQL 升级时使用了 --clone
或 --link
参数,而升级失败:
-
使用
--link
(硬链接模式):由于新旧集群共享相同的文件数据块,一旦升级过程中出现问题并修改了这些共享文件,原集群的数据可能会被破坏或变得不一致,导致无法直接回滚到旧集群。只有在升级成功完成前未启动新集群的情况下,旧集群才有可能通过手动恢复pg_control
文件等方式重新使用;否则需要从备份中恢复旧集群。 -
使用
--clone
(写时复制/ reflink 模式):与--link
类似,--clone
也依赖于底层文件系统的写时复制机制来实现快速复制。如果升级失败,虽然原始数据通常不会直接受损(因为它是按需复制的),但如果新集群已经开始写入数据,则可能导致部分数据不一致,并且旧集群可能无法再安全地启动,除非有完整的备份。