水族馆

在容器内使用 pg_upgrade 快速升级 PostgreSQL 大本版

阅读时间 1 分钟


我计划将 PostgreSQL 从 15 版本升级到 17 版本。跨越大本版升级,需要手动干预。官方提供了一个名为 pg_upgrade 的工具,可以实现集群的原位升级,无需从旧集群导出数据再恢复到新集群。

pg_upgrade官方文档 描述的是通过 tarmake install 等方式安装的 PostgreSQL 升级流程。网上许多教程都声称如果使用 Docker 就必须导出再导入所有数据。

我不认同这种说法,决定尝试在 Docker 容器内实现原位升级。另一个重要原因是我们的业务要求数据库停机时间最短。

根据文档,pg_upgrade 支持克隆 (Clone) 或链接 (Link) 模式,能大幅提升升级速度。以我的测试为例,300GB 的数据库升级耗时不到 10 秒。

链接模式和克隆都需要文件系统支持特定功能。我测试过 ZFSBTRFS,基本上所有支持写时复制 (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

此时工作目录包含:

创建新数据库

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

各个参数说明:

  1. -v ./data-17:/var/lib/postgresql/data 挂载新数据目录 gg
  2. -v ./data:/data 挂载旧数据目录
  3. -v ./usr-lib-15:/usr/lib/postgresql/15 挂载旧版二进制文件
  4. -v ./usr-share-15:/usr/share/postgresql/15 挂载旧版支持文件
  5. --user 999 --workdir /var/lib/postgresql/data 确保 pg_upgrade 有写入权限
  6. -e POSTGRES_PASSWORD 设置密码(若非默认 postgres 用户需额外配置)
  7. --clone 原地执行数据升级操作

如果在使用 pg_upgrade 进行 PostgreSQL 升级时使用了 --clone--link 参数,而升级失败: