跳至内容
14.4 生命周期与升级耦合

14.4 生命周期与升级耦合

扩展有两条相互耦合、却不会自动同步的生命周期:

node lifecycle:
  repository -> package -> control/SQL/library -> preload/restart

database lifecycle:
  CREATE EXTENSION -> member objects -> ALTER UPDATE -> DROP

运维事故往往发生在两条线暂时分离时:包已升级而对象未升级、对象已写入 catalog 而新备库缺库、备份完整却恢复环境没有旧脚本。

14.4.1 安装版本不等于数据库对象版本

三个“版本”不要合成一个字段

至少记录:

名称来源示例
项目/release 版本upstream release/package metadatapgvector 0.8.4
节点软件包 buildrpm/deb/imagevendor release + PG18 + OS
数据库对象版本pg_extension.extversionvector 0.8.4

有些包一次提供多个 SQL 对象版本和更新脚本。于是:

package release = newest support files
extversion      = current objects in one database

两者不同并不必然错误,但必须是被管理的过渡状态。

先装包,再逐库迁移

一个安全的普通更新顺序:

1. freeze exact package build
2. read control/release/update scripts
3. install package on standbys and primary nodes
4. verify files and loadability on every node
5. rehearse on restored/cloned database
6. inventory every database extversion/owner/dependency
7. establish query + plan + correctness baseline
8. ALTER EXTENSION ... UPDATE in controlled window
9. verify catalog/member/behavior/log/replication
10. observe before removing old support files

为什么先把文件铺到所有节点?因为 failover 不应把一个刚完成对象更新的 数据库交给缺动态库的备库。物理复制会复制数据目录变化,不会替你把 $libdir 文件复制到另一台主机。

为什么逐库?扩展是 database-scoped。一个 cluster 中:

\l

列出的每个数据库都有自己的 pg_extensionpostgres 已经 1.6,不代表 appanalyticstemplate 也已经 1.6。

建立跨节点、跨数据库矩阵

节点侧:

pg_config --version
pg_config --sharedir
pg_config --pkglibdir

# 由包管理器/CMDB采集 exact build;不要解析一个浮动别名当版本

数据库侧:

SELECT
    current_database() AS database_name,
    e.extname,
    e.extversion,
    pg_get_userbyid(e.extowner) AS owner,
    n.nspname AS nominal_schema
FROM pg_extension AS e
JOIN pg_namespace AS n
  ON n.oid = e.extnamespace
ORDER BY e.extname;

聚合后应能回答:

node A/B/C package build
  × database D1/D2/D3 extversion
  × primary/standby role

只保存 \dx 截图无法发现某台备库缺包,也无法显示其他数据库。

control 默认版本不会升级已有对象

升级包后:

SELECT
    name,
    default_version,
    installed_version
FROM pg_available_extensions
WHERE name = 'pg_trgm';

可能显示:

default_version=1.6
installed_version=1.3

含义是:

  • 新执行 CREATE EXTENSION pg_trgm 默认创建 1.6;
  • 当前数据库对象仍是 1.3;
  • 只有显式 ALTER EXTENSION ... UPDATE 才迁移它。

不要靠重跑:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

期待升级。IF NOT EXISTS 只会 notice 并保留现有对象,也不保证同名现有 对象就是期望内容。

更新前读脚本,不只读 release notes

查看路径:

SELECT *
FROM pg_extension_update_paths('pg_trgm')
WHERE source = '1.3'
  AND target = '1.6';

再读取实际 package 中:

pg_trgm--1.3--1.4.sql
pg_trgm--1.4--1.5.sql
pg_trgm--1.5--1.6.sql

评审:

  • 是否改类型或存储格式;
  • 是否重建/重写索引;
  • 是否触碰扩展配置表;
  • 是否删除/重命名函数与操作符;
  • 是否会扫描业务数据;
  • 需要何种锁;
  • 失败是否能事务回滚;
  • 更新后的旧应用是否仍兼容。

本章对这些文件和动态库做 SHA-256,保证复验时读的是同一构建;哈希不替代 代码评审。

package rollback 不等于 object rollback

假设已经把对象从 1.3 更新到 1.6,再把 OS 包降回只支持 1.3:

database extversion=1.6
filesystem supports only 1.3

这是更危险的不一致。反向对象迁移只有在扩展明确提供 downgrade path 且 数据格式兼容时才可能。更常见的回退是:

  • 在更新前保留可恢复备份/快照;
  • 在 clone 上验证;
  • 更新后向前修复;
  • 若必须回退,恢复到更新前一致时间点并协调业务数据。

因此扩展更新的“可回滚”不能只写 apt downgrade

14.4.2 大版本升级、备份恢复与逻辑复制兼容

pg_upgrade 不会替你验证外部模块

PostgreSQL 官方 pg_upgrade 文档明确提醒:所有外部模块必须与新 server 二进制兼容;pg_upgrade 无法检查这一点。新集群主库与备库都要安装匹配 shared libraries。

大版本升级前,对每个扩展冻结:

old server major/build
old extension object version
old package build

new server major/build
new-compatible package build
target extension object version
supported transition order

可能的顺序取决于扩展:

old PG: update extension to prerequisite version
  -> install new-PG-compatible files
  -> pg_upgrade / logical migration
  -> new PG: ALTER EXTENSION to target version

也可能要求另一个顺序。以扩展的目标版本升级文档为准。

pg_upgrade --check 通过不等于扩展可用。clone rehearsal 至少要:

  • 启动新集群;
  • 查询每个扩展类型/函数;
  • 检查 expression index/opclass;
  • 重建或验证要求重建的索引;
  • 跑应用回归;
  • 启动新备库;
  • 验证 dump/restore 和监控。

物理备份包含数据,不包含 OS 供应链

物理备份复制数据库文件和 WAL。它不会自动保存:

  • PostgreSQL server binary;
  • control 与版本 SQL;
  • 动态库;
  • preload 配置的外部部署来源;
  • OS package repository。

恢复手册必须能重建:

compatible server binary
  + exact/compatible extension packages
  + configuration/preload
  + data directory and WAL

只保留最新仓库,未必能恢复三年前依赖旧扩展对象版本的备份。长期保留策略要 考虑 package snapshot、image digest 或可复现构建。

逻辑 dump 用声明恢复扩展

PostgreSQL 把扩展视为整体。全库 schema-only dump 中,本章看到:

CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA shop_ch14;
COMMENT ON EXTENSION pg_trgm IS '...';

CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA shop_ch14;
COMMENT ON EXTENSION vector IS '...';

却没有:

CREATE TYPE shop_ch14.vector ...
CREATE FUNCTION shop_ch14.similarity ...

这正是扩展机制的 dump 合同。恢复顺序隐含要求:

target support files available
  -> schema/extension creation
  -> dependent application tables/indexes
  -> data

本章还证明一个容易忽略的选择性 dump 语义:

pg_dump --schema-only --schema=shop_ch14 ...

输出包含:

candidate_doc table
vector column
GIN/HNSW indexes

但不包含 CREATE EXTENSION。PostgreSQL pg_dump 文档对 --schema 选择明确警告:它不保证自动带上所选对象依赖的所有对象。这个 artifact 不能单独在洁净环境恢复,必须由恢复清单显式先创建扩展。

参见 pg_dump

clean restore 是唯一有力的恢复证据

不要在原集群上执行 dump 后立刻宣称可恢复。洁净环境要求:

  • 没有预装数据库扩展对象;
  • 使用冻结 server/package build;
  • 从空 database 开始;
  • 按 runbook 恢复;
  • 验证 extension owner/schema/version/member;
  • 验证业务行数/checksum;
  • 验证查询、计划与权限;
  • 记录时间、日志和失败。

若恢复必须“手工试几个版本直到成功”,供应合同尚未完成。

逻辑复制不复制 schema 与扩展生命周期

内置逻辑复制主要复制表数据变更,不替你复制 DDL、extension control files 或 CREATE EXTENSION。发布端列使用自定义类型时,订阅端必须预先拥有可 接受该列值、函数和索引的兼容 schema。

评审:

  • publisher/subscriber 类型名与语义;
  • text/binary 传输与转换能力;
  • extension object version;
  • DDL 发布顺序;
  • replica identity;
  • extension-owned 配置/metadata 是否作为普通表复制;
  • 订阅端触发器/默认值/约束的执行差异;
  • major/architecture 组合。

“两端都显示 extension installed”仍不足以证明版本/数据语义兼容。

如果逻辑复制被用作迁移出口,先把自定义数据转换为内置交换类型通常更容易 控制。例如本章把 vector(3) 显式转为 text,而不是要求目标立即加载 同一 extension。

参见 Logical Replication Restrictions

备库与 failover

物理 standby 会重放创建表、类型依赖和 extension catalog 变化,但不会 运行节点包管理器。变更前:

all standbys have compatible support files
  -> preload/config staged
  -> restart completed if required
  -> primary database DDL/update
  -> replication caught up
  -> controlled switchover/failover probe

检查不能只 SSH 到主库。新加入节点、灾备节点、延迟副本和备份 restore worker 都属于供应范围。

14.4.3 依赖扩展不可用时的降级策略

先区分必需能力与增强能力

扩展依赖可分:

类型例子不可用时
数据可读必需业务列是自定义类型数据库/查询可能无法正常使用
写入必需trigger/function 是写入合同应停止写而不是绕过不变量
查询增强可重建索引/opclass可回退较慢原生查询
观测增强统计/采样扩展核心业务可运行,诊断能力下降
维护增强repack/调度工具延后维护并告警

只有后两三类适合真正“降级”。把自定义列类型说成可选能力是自欺。

设计能力探测,但不要每次请求查 catalog

发布/启动时探测:

SELECT
    e.extname,
    e.extversion
FROM pg_extension AS e
WHERE e.extname IN ('pg_trgm', 'vector');

再验证所需签名与索引:

SELECT to_regprocedure('shop_ch14.similarity(text,text)');
SELECT to_regclass('shop_ch14.candidate_doc_title_trgm_idx');

结果进入部署 gate 或低基数健康状态,而不是每个请求动态猜。应用 feature flag 必须与数据库迁移阶段同步:

extension absent:
  extension-dependent feature disabled

extension installed and validated:
  canary reads

index built and valid:
  limited traffic

observation passed:
  normal traffic

可重建索引的降级

pg_trgm 例子:

normal:
  title % $query
  GIN gin_trgm_ops

degraded:
  exact normalized equality
  or prefix lookup
  or PostgreSQL FTS

降级查询语义不同,API 要明确:

  • 是否返回较少结果;
  • 是否暂停 fuzzy mode;
  • 延迟是否提高;
  • 哪些 SLO 暂时失效。

不要悄悄返回不同业务含义。

自定义类型的退场顺序

vector 为例:

1. freeze model/dimension and export format
2. add destination native/external representation
3. backfill with row/checksum verification
4. deploy dual-read or switched-read application
5. stop new extension-type writes
6. verify no view/function/index/table depends on type
7. drop extension-dependent indexes/columns
8. DROP EXTENSION without CASCADE
9. remove preload if any and then node package

依赖检查:

-- 先看 pg_depend 和业务对象定义
\d+ shop_ch14.candidate_doc
\dx+ vector

-- 最后的 DROP 必须用 RESTRICT 语义暴露遗漏
DROP EXTENSION vector;

绝不以:

DROP EXTENSION vector CASCADE;

作为“清理方便”的生产脚本。CASCADE 会把尚未迁走的业务对象一起删除。

当动态库临时缺失

若 catalog 已有扩展而节点缺 library:

  • 不要继续 failover 到该节点;
  • 阻断相关流量或节点晋升;
  • 从受控仓库恢复匹配包;
  • 验证哈希、loadability 与查询;
  • 检查所有其他节点是否同样漂移;
  • 解释配置管理为何未发现;
  • 完成恢复/备库回归。

删除 catalog 中 extension 不是修动态库缺失的第一反应,尤其当业务类型依赖 它时。

降级 SLO

ADR 预先定义:

feature: fuzzy-title-search
dependency: pg_trgm
failure_detection: deployment probe + query error alert
fallback: normalized-prefix-search
semantic_change: typo tolerance disabled
latency_budget: 200ms
maximum_duration: 2h
owner: search-team
restore_action: package parity + catalog/query verification

没有时间、语义和 owner 的 fallback 只是愿望。

本节结论

扩展生命周期的真正完成条件:

can install
  + can update
  + can fail over
  + can back up and clean-restore
  + can cross major
  + can degrade or stop safely
  + can exit without CASCADE

CREATE EXTENSION 只完成第一项的一部分。


上一节:扩展选型的六个问题 · 返回本章目录 · 下一节:用 Pigsty 管理扩展可用性 · 查看全书目录 · 查看索引中心

最后更新于