概述
- PG 主版本的升级可能会引入新特性以及新的系统表,但表数据的存储格式很少更改。借助这一点,
pg_upgrade
就可以通过直接拷贝旧的用户表数据文件来实现快速升级,而用户数据库对象的定义则是通过调用pg_dump/pg_restore
从旧实例中迁移到新实例中的。如果表数据的存储格式发生了变化导致不兼容,则pg_upgrade
将无法工作,不过 PG 社区会尽量避免这种情况的发生; - 使用要点:
- 只能从低版本升级到高版本;
- 可以跨版本升级,比如从 v11 可以直接升级到 v16;
- 使用 pg_upgrade 进行升级时,需要同时安装新旧两个版本的 PG,此时应该使用新版本的 pg_upgrade 进行升级;
- 如果指定了
--check
选项,则 pg_upgrade 只检查新旧实例的兼容性而不进行真正的升级操作,所以允许旧实例处于运行状态,否则新旧实例都必须处于停机状态; - 新实例需要用户手动调用
initdb
进行创建,相关的兼容性参数应该和旧实例保持一致,比如--wal-segsize
、--locale
; - 在升级期间,用户和其他应用程序不应该在访问新旧实例,而 pg_upgrade 默认会使用 50432 端口先后启动新旧实例;
- pg_upgrade 调用 pg_dump 时,会指定
--upgrade-mode
选项,这是专门用于升级的选项; - 如果使用了
--link
选项,则数据文件会被新旧实例共享,会产生数据竞争; - pg_upgrade 不会自动同步 postgresql.conf、pg_hba.conf 等配置到新实例中,这需要用户手动修改;
- pg_upgrade 不会自动同步全文搜索文件到新实例中,这需要用户进行手动复制;
- pg_upgrade 不会自动同步优化统计信息,用户需要手动执行 analyze_new_cluster.sh;
- pg_upgrade 不会自动升级插件的版本,用户需要手动执行 update_extensions.sql;
- 在高可用和分布式的部署模式下,每个节点都需要单独执行 pg_upgrade 进行升级;
pg_upgrade
的选项
启动数据库相关的选项
pg_upgrade
执行期间可能会启动新旧实例,以下是启动数据库所需的选项,这些选项都是对称的(小写字母的选项用于指定旧实例的信息,大写字母的则用于新实例):
长选项 | 短选项 | 描述 | 等价环境变量 |
---|---|---|---|
--old-bindir | -b | 旧实例的 bin 目录 | PGBINOLD |
--new-bindir | -B | 新实例的 bin 目录 | PGBINNEW |
--old-datadir | -d | 旧实例的 data 目录 | PGDATAOLD |
--new-datadir | -D | 新实例的 data 目录 | PGDATANEW |
--old-port | -p | 旧实例的端口号 (默认 50432) | PGPORTOLD |
--new-port | -P | 新实例的端口号 (默认 50432) | PGPORTNEW |
--old-options | -o | 启动旧实例时,传递给 pg_ctl 的选项 | |
--new-options | -O | 启动新实例时,传递给 pg_ctl 的选项 |
- 上述部分选项可以通过等价的环境变量来指定,比如可以通过配置 PGBINOLD 来实现 -b 选项的作用;
-B
选项和环境变量PGBINNEW
不是必须的,未指定则默认为pg_upgrade
的当前目录;
文件拷贝相关的选项
在进行文件拷贝时,pg_upgrade 支持使用不同的拷贝模式,以下是文件拷贝相关的选项:
- 直接拷贝文件(默认方式);
- 通过硬链接实现;
- 通过引用链接实现(这需要操作系统和文件系统支持引用链接的特性,否则运行出错);
长选项 | 短选项 | 描述 |
---|---|---|
--link | -k | 复制数据文件时使用硬链接 |
--clone | 复制数据文件时使用引用链接 |
其他控制选项
长选项 | 短选项 | 描述 | 等价环境变量 |
---|---|---|---|
--socketdir | -s | 套接字目录 (默认当前目录) | PGSOCKETDIR |
--check | -c | 只检查而不进行真正的升级 | |
--jobs | -j | 并行执行的进程或线程数量 | |
--retain | -r | 成功升级依然保留 SQL 和日志 |
--jobs
:Windows 平台是通过多线程实现并行执行,其余平台则是通过多进程的方式;
13.8 版本的 pg_upgrade
流程分析
校验新旧实例的兼容情况
- 校验新旧实例二进制目录和数据目录的有效性;
- 校验新旧实例的版本是否支持升级,版本信息是通过调用
pg_ctl --version
来获取的; - 校验新旧实例的二进制兼容性,二进制信息是通过调用
pg_controldata
来获取的;
获取并校验旧实例的信息
- 启动旧实例,通过 libpq 连接到服务器,使用 template1 数据库进行连接;
- 获取数据库信息,并依次获取各数据库中部分表的信息:
- 数据库信息是通过查询 pg_database 系统表,获取的数据库不包括 template0;
- 只获取各数据库部分表的原因在于这些表对应的数据文件后续会被直接拷贝到新实例中,包括:
- 用户定义的普通表、物化视图、序列;
- 上述表对应的 toast 表;
- 上述普通表、物化视图、序列以及 toast 表所对应的 index 表;
- 获取旧实例各数据库中加载的 C 动态库名称:通过查询 pg_proc 系统表的 probin 列;
- 校验旧实例中可能导致升级失败的情况:
- 进行升级操作的用户必须是
initdb
旧实例时的超级用户; - 除 template0 外,旧实例中的其余数据库都必须是可连接的;
- 旧实例没有处于准备状态的提交事务,因为其存储格式可能发生变化;
- 旧实例中的用户表不可以使用系统定义的复合类型 (比如 pg_class、pg_type) ,因为这些类型在各版本间可能实现不同,出错时信息会记录到 tables_using_composite.txt 文件中;
- 旧实例中的用户表不可以使用系统定义的 reg* 类型 (比如 regnamespace、regconfig) ,因为这些类型在各版本间可能实现不同,出错时信息会记录到 tables_using_reg.txt 文件中。这里有一个例外,就是 regclass、regrole、regtype 这三个类型是可以使用的;
- 新旧实例中 int8 和 float8 类型作为函数参数时的传递方式必须一致,都是值传递或引用传递 (ISN 插件的实现就依赖了 PG 的 int8 类型) ,出错时信息会记录到 contrib_isn_and_int8_pass_by_value.txt 文件中 (注意:这里的 int8 和 float8 指的是 PG 的数据类型,它们的大小都是 64 位) ;
- 进行升级操作的用户必须是
从旧实例中导出
如果指定了 --check
选项,则跳过该步骤 ;
- 导出全局数据库对象:
- 通过调用
pg_dumpall --globals-only
;
- 通过调用
- 按照之前查询的数据库信息,导出各个数据库中的数据库对象:
- 通过调用
pg_dump --schema-only --binary-upgrade --format=custom
(表数据是不导出的) ; - 如果指定了
--jobs
选项,则导出操作可以并行执行;
- 通过调用
- 导出完成后还会停止旧实例;
获取并校验新实例的信息
- 在检验会启动新实例,并按照和旧实例完全相同的方式获取新实例中的数据库信息以及其中部分表的信息;
- 校验新实例中可能导致升级失败的情况:
- 新实例中不能有用户定义的表 (pg_upgrade 目前只会检测表对象,没有检测 schema、类型 等其他类型的对象) ;
- 新旧实例中同名数据库的编码、排序规则、字符分类规则必须相同;
- 新实例必须提供旧实例会使用到的 C 动态库,出错时信息会记录到 loadable_libraries.txt 文件中 (通过执行
LOAD
指令进行检测的) ; - 如果指定了
--link
或--clone
选项,则操作系统和文件系统必须支持该特性; - 进行升级操作的用户必须是
initdb
新实例时的超级用户; - 新实例没有处于准备状态的提交事务;
- 新实例表空间对应的目录不能已存在;
升级新实例
如果指定了 --check
选项,则程序就此退出不再进行以下操作,因为以下操作会真正改动新实例数据目录的内容:
- 调用
vacuumdb --all --analyze
以及vacuumdb --all --freeze
; - 停止新实例,拷贝
pg_xact/
和pg_multixact/
目录中的文件,并为新实例指定下一个 XID 和 MultiXact ID; - 启动新实例,将导出的内容导入到新新实例中:
- 通过调用
psql
将全局数据库对象导入到新库; - 通过调用
pg_restore --create --dbname template1
对各个库进行导入;- 在导入 postgres 和 template1 库时,
pg_restore
还会额外指定--clean
选项; - 如果指定了
--jobs
选项,则导入操作可以并行执行。这里有一个特殊处理,template1 库必须先导入,且不能和其他库一起并行导入,因为 template1 库在恢复时会先被删除,而其他库在并行恢复时是通过 template1 进行连接的;
- 在导入 postgres 和 template1 库时,
- 再次按照相同的方式获取新实例中的数据库信息以及其中部分表的信息,供之后迁移数据文件时使用;
- 通过调用
- 停止新实例,迁移表对应的数据文件:
- 根据实际选项,使用拷贝、链接或克隆的方式将数据文件从旧实例迁移到新实例中;
- 如果指定了
--jobs
选项,则迁移操作可以并行执行。但每个并行负责一个表空间目录的迁移,所以如果只有一个表空间,实际并行的最大数量也只会是 1 (注意:一个表空间目录可能存放的是来自多个数据库的数据文件) ;
- 为新实例设置下一个 OID:
- 通过调用
pg_resetwal
;
- 通过调用
- 同步数据目录到磁盘;
- 通过调用
initdb --sync-only
;
- 通过调用
- 生成 analyze_new_cluster.sh 脚本,用于收集新实例的统计信息 (需手动执行) ;
- 生成 delete_old_cluster.sh 脚本,用于删除旧实例的数据目录和表空间版本子目录 (需手动执行) ;
- 生成 update_extensions.sql 脚本,用于升级新实例中的插件 (需手动执行) ;
故障排除所涉及的日志文件
pg_upgrade_internal.log | pg_upgrade 程序本身生成的日志 |
pg_upgrade_server.log | 新旧实例的服务器运行日志 |
pg_upgrade_dump_*.log | pg_dump/pg_restore 的日志 |
pg_upgrade_utility.log | 调用 psql、pg_resetwal、pg_dumpall 等工具所产生的日志 |