内容提要
本文介绍使用 pg_upgrade --link 对 Spock 多主集群进行逐节点原地升级(从 17.10 升至 18.6)。升级期间节点短暂停机,其余节点仍可继续提供写入。主要注意事项:Spock 复制起点不会随 pg_upgrade 迁移,需按提供方槽位重建,否则节点只发不收且状态仍显示正常;Spock 参数须在升级前写入新集群;PG18 默认启用校验和,若旧集群未启用则需加 --no-data-checksums;新系统标识会导致 Patroni 无法恢复,需从 etcd 删除集群记录后重新接管;备库须清空目录重建;ALTER SYSTEM 设置会丢失。
延伸解读
复制起点不迁移:最隐蔽的“只发不收”故障
文章强调,pg_upgrade 不会迁移 Spock 用于记录订阅应用进度的 replication origin,只处理原生 PostgreSQL 订阅。升级后的节点仍能向其他节点发送变更,但无法应用来自其他节点的变更,而其他节点上的订阅状态可能仍显示正常。修复方法是从提供方槽位读取 confirmed_flush_lsn,在新节点上重建 origin 并推进到该位置。仅看订阅状态无法发现此问题,必须做双向写入测试。
升级前必须完成的配置与检查
新集群的 Spock 参数必须在运行 pg_upgrade 之前写入 postgresql.conf,否则 pg_upgrade --check 会因 shared_preload_libraries 无法加载 spock 而失败。同时要确认新旧集群的 Spock 版本一致,并注意 PostgreSQL 18 的 initdb 默认启用数据校验和;若旧集群未启用,需显式加 --no-data-checksums,否则检查阶段会直接报错退出。这些前置条件不满足时,升级无法开始。
Patroni 与备库:系统标识变化带来的额外操作
pg_upgrade 会给集群分配新的系统标识,Patroni 在暂停模式下恢复时会因此退出。文章的做法是先停止 Patroni,从 etcd 删除集群记录,再让 Patroni 重新接管运行中的实例;这要求 Spock 参数已写入 bootstrap.dcs,否则 Patroni 会用默认值覆盖 wal_level 和 shared_preload_libraries。物理备库无法跨大版本跟随主库,需清空备库目录后由 Patroni 自动执行 pg_basebackup 重建,而不是尝试 reinit。
升级后仍需补做的收尾工作
pg_upgrade 不迁移 postgresql.auto.conf,因此通过 ALTER SYSTEM 设置的参数(如 synchronized_standby_slots)会丢失,需要重新设置。升级完成后还应执行 pg_upgrade 提示的后续步骤:运行 update_extensions.sql 更新扩展,并用 vacuumdb 重建统计信息。确认新集群工作正常后,再删除旧集群并恢复 DDL 复制。文章建议逐节点重复上述流程,并在接触下一节点前完成双向写入验证。
Q&A
用 pg_upgrade --link 对 Spock 多主集群做原地大版本升级,升级期间应用会完全不可写吗?
不会。升级时被升级的节点会短暂停机,但多主集群中的其他节点仍可继续接受写入,因此应用始终有可写的端点。停机只影响单个节点,不影响整体写入。
为什么 pg_upgrade 之后 Spock 节点只发送数据却收不到其他节点的变更,而且状态还显示正常?
因为 Spock 用 replication origin 记录每个订阅的复制进度,而 pg_upgrade 不会迁移这些 origin(只迁移原生 PostgreSQL 订阅的)。缺少 origin 时,升级后的节点会发送变更但从不应用收到的变更,其他节点仍显示订阅为 replicating,所以状态看起来正常。修复方法是根据提供方槽位的 confirmed_flush_lsn 重建每个 origin。
升级到 PostgreSQL 18 时,如果旧集群没有启用数据校验和,initdb 和 pg_upgrade 需要注意什么?
PostgreSQL 18 的 initdb 默认启用数据校验和,所以如果旧集群没有校验和,仅省略 --data-checksums 不够,pg_upgrade --check 会报错。必须显式使用 --no-data-checksums 初始化新集群,使新旧集群的校验和设置一致。
pg_upgrade 完成后 Patroni 无法恢复节点,提示 system ID 已改变,该怎么处理?
pg_upgrade 会给集群生成新的 system identifier,而 etcd 中仍保存旧的。需要先停止 Patroni,用 patronictl remove 从 etcd 删除该集群记录,再重新启动 Patroni 让它接管正在运行的实例。注意 Spock 参数必须放在 Patroni 配置的 bootstrap.dcs 中,否则 Patroni 会用默认值覆盖 wal_level 和 shared_preload_libraries。
升级主节点后,原来的物理备库能否直接 reinit 跟随新主?
不能。物理备库无法跨大版本跟随主库,且尝试 reinit 会因 system ID 不匹配而失败,备库的 Patroni 会退出。正确做法是清空备库数据目录,再启动其 Patroni,让它自动从新主库执行 pg_basebackup 重建。
pg_upgrade 升级后,之前用 ALTER SYSTEM 设置的参数(如 synchronized_standby_slots)还在吗?
不在。pg_upgrade 不会迁移 postgresql.auto.conf,所有通过 ALTER SYSTEM 设置的参数都会丢失,需要在升级后重新设置,例如重新执行 ALTER SYSTEM SET synchronized_standby_slots 并 reload。
在运行 pg_upgrade 之前,为什么必须先把 Spock 相关参数写入新集群的 postgresql.conf?
因为 Spock 必须位于 shared_preload_libraries 中才能加载,而 pg_upgrade 需要在新集群中加载它。如果新集群没有提前配置好 Spock 参数,pg_upgrade --check 会因无法加载 spock 库而失败。因此要在升级前把新节点的 Spock 参数配置成与旧节点完全一致。