跳转到主要内容

14 博采众长:内核分支与扩展生态

PostgreSQL 的扩展生态很强,但“有这个扩展”不是架构理由,“能够安装”也 不是生产结论。一个扩展进入数据库后,可能同时改变:

  • 节点上的软件包、控制文件、SQL 脚本与动态库;
  • 数据库里的类型、函数、操作符、访问方法和系统目录依赖;
  • 主库、备库、备份恢复、逻辑订阅与大版本升级的前置条件;
  • 安装、升级和删除所需的特权;
  • 应用数据的可移植性、故障半径与退出成本。

因此本章不做“常用扩展清单”,而是建立一套可复用的治理方法:

先证明问题,再检查原生替代;先冻结版本与运行条件,再创建数据库对象; 先演练升级、恢复和退出,再允许业务依赖。

第 15–17 章会分别深入检索、时空与分析/分布式能力。本章负责给它们提供 同一把尺子,避免每遇到一个新扩展就重新发明评审标准。

本章完成后

你应当能够:

  • 区分 PostgreSQL server、发行版/内核、OS 软件包、扩展支持文件与数据库 扩展对象;
  • .control、版本 SQL、动态库与 pg_extension 解释一个扩展如何 被发现、安装和拥有;
  • 解释 superusertrustedrelocatablerequiresshared_preload_libraries 各控制什么;
  • pg_available_extensionspg_available_extension_versionspg_extension_update_paths()pg_depend 取得原生证据;
  • 不把“兼容 PostgreSQL”误解为扩展、目录、运维和故障语义都兼容;
  • 用六个问题筛选扩展,而不是按热度、功能数量或安装便利度决策;
  • 区分软件包版本与数据库对象版本,并设计受控的 ALTER EXTENSION UPDATE
  • 把备份恢复、物理备库、逻辑复制与 pg_upgrade 纳入扩展生命周期;
  • 在 Pigsty 中区分仓库下载、节点安装、预加载配置和数据库启用;
  • 解释包别名 pgvector 与 SQL 扩展名 vector 为什么不能混用;
  • 识别节点漂移、control file 缺失、动态库缺失、未预加载与对象版本漂移;
  • 写出包含问题、成功标准、供应链、权限、升级、恢复和退出的扩展 ADR;
  • 对同一批候选给出“接受、限域试点、当前拒绝”三种有证据的结论。

三层状态,四个动作

扩展治理首先要拆开三层状态:

供应层
  repository/package/image
    └─ control + version SQL + shared library

进程层
  shared_preload_libraries / server restart / loadability
    └─ backend can load the module

数据库层
  pg_extension + member objects + extversion
    └─ this database can use the SQL interface

Pigsty 把典型流程组织成四个动作:

Download -> Install -> Configure -> Enable
仓库下载     节点装包    预加载/参数    CREATE EXTENSION

四步并非每个扩展都全部需要:

  • pg_trgm 有数据库对象,但不需要 shared preload;
  • vector 有数据库对象和动态库,本章版本也不需要 shared preload;
  • wal2json 是逻辑解码插件,装包后按插件名使用,并不要求 CREATE EXTENSION
  • Citus、TimescaleDB 等扩展有额外预加载、拓扑或生命周期要求,必须查目标 版本文档,不能类推。

反过来,完成最后一步也不能证明前面三层在所有节点一致。主库已经 CREATE EXTENSION,某台备库仍可能缺动态库;数据库目录显示 0.8.4, OS 仓库却已经换成另一构建。每层都需要独立证据。

贯穿实验:三个候选,三种结论

本章围绕 shop_ch14 评审三个候选:

问题 候选 结论 核心理由
单字段拼写容错 pg_trgm accept 问题有界、contrib、受信任、GIN 可证、退出不锁定列类型
语义近邻检索 vector pilot 能力成立,但模型、质量、资源、恢复与退出仍需真实语料证明
分布式分片 citus reject now 尚无单节点上限证据、分片键合同和跨分片事务设计

“拒绝”不表示 Citus 有缺陷。Pigsty 当前扩展目录提供 Citus,说明平台有 交付能力;本章仍拒绝它,是因为供应能力不能替代问题适配。

实验故意让权限差异可见:

pg36_owner (non-superuser, database owner)
  ├─ CREATE EXTENSION pg_trgm VERSION '1.3'  -> success
  │    trusted=true, extension owner=pg36_owner
  └─ CREATE EXTENSION vector VERSION '0.8.4' -> SQLSTATE 42501
       trusted=false, admin approval required

postgres/admin
  └─ CREATE EXTENSION vector -> success
       extension owner remains a superuser

pg36_app
  ├─ SELECT reviewed tables and use operators -> success
  └─ ALTER EXTENSION pg_trgm UPDATE            -> SQLSTATE 42501

随后由 pg36_ownerpg_trgm 从 1.3 更新到 1.6。更新前后:

trigram top ids = 1,5,2
vector top ids  = 1,2,5
trigram plan    = GIN bitmap index scan
vector plan     = HNSW index scan

这只证明固定五行夹具的机制与回归合同,不证明生产相关性、召回率或尾延迟。

实验资产

完整合同与入口:

正式本地证据在 Homebrew PostgreSQL 18.6 上采集。正文把平台映射核对到 Pigsty 4.5,但没有在 Pigsty L1 主机上运行,因此输出明确记录:

validation_path=direct-postgresql
pigsty_l1=not-run

这不是缺点掩饰,而是证据边界。读者在自己的 L1 上必须补采仓库、各节点 包版本、预加载和数据库对象状态。

快速运行

沿用前章受控的 libpq service:

export PGSERVICEFILE=/path/to/pg_service.conf
export PGSERVICE=pg36-admin

PG36_EVIDENCE_DIR="$PWD/evidence/ch14" \
  ./static/labs/ch14/task.sh all

all 会:

  1. 验证数据库、角色、ch04-v1 模型和基础业务不变量;
  2. 对目标 schema 与同名扩展执行 marker/owner/version 碰撞保护;
  3. 以 owner 安装受信任的 pg_trgm 1.3;
  4. 证明 owner 安装未受信任的 vector 返回 42501
  5. 由管理员安装 vector 0.8.4,建立 GIN 与 HNSW 夹具;
  6. 采集可用版本、control 属性、成员、ACL、索引和更新路径;
  7. 证明应用可查询但不能升级扩展;
  8. 记录升级前查询与强制索引计划;
  9. pg_trgm 更新到 1.6,再次记录相同证据;
  10. 对 control、安装/更新 SQL 与动态库生成 SHA-256;
  11. 对比全库 schema dump 与 --schema=shop_ch14 选择性 dump;
  12. 把向量转为文本导出,形成试点退出材料;
  13. 验证错误 token、错误 target 和活跃 worker 下的 reset 拒绝;
  14. 不使用 CASCADE 精确复位,再从零完整重建和复验。

成功摘要:

status=ok
decision=pg_trgm:accept/vector:pilot/citus:reject
boundary=package+control+database-object
failure=42501-owner+42501-superuser
upgrade=pg_trgm:1.3->1.6-behavior-stable
index=gin+hnsw
dump=create-extension+selective-dependency-warning
exit=portable-text-export
pigsty_l1=not-run
release=1.2-proposal
release_candidate_checksum=6a4b74baec5f522eb098c868f1d4f1b441bf5b5f6708411588af0a8793f7f573

这个 proposal checksum 标识 ADR、版本、夹具和验收合同;运行时间、绝对 安装路径以及文件系统 inode 不进入业务 golden。

安全边界

task.sh all 会删除并重建专用 shop_ch14,并删除带本章精确 marker 的 pg_trgmvector。它只适合本书本地/开发夹具。生产安装和升级必须 使用分阶段迁移、备库检查、恢复演练、观察窗口和独立回退,不运行 “先删后建”的教学入口。

学习路径

14.1 PostgreSQL 扩展机制

先把扩展还原为 PostgreSQL 原生对象和支持文件。不了解这层,就无法解释 为什么“包已安装”和“数据库可用”不是同一件事。

14.2 内核、发行版与托管服务

再把扩展放回具体供应环境,建立 SQL、协议、目录、扩展与运维五层兼容矩阵。

14.3 扩展选型的六个问题

这一节把“喜欢哪个扩展”转化为六个可反驳、可采证的问题。

14.4 生命周期与升级耦合

安装只是生命周期起点。真正的承诺发生在升级、恢复、复制和退出时。

14.5 用 Pigsty 管理扩展可用性

把原生机制映射到 Pigsty 4.5,但始终回到节点文件、live 参数和系统目录 复核。

14.6 建立可复用扩展 ADR

把讨论沉淀为能够被后续章节复用、被版本变化触发复审的决策记录。

14.7 实战:评审三个候选扩展

最后把包、权限、对象、查询、升级、dump、出口和复位压成一份可审计交付物。

版本与证据边界

本章原理以 PostgreSQL 14–18 为范围;可执行 baseline 固定 pg_trgm 1.3/1.6、vector 0.8.4,并在 PostgreSQL 18.6 上验证。目标环境 没有这些精确版本时,不应伪造通过,而应复制 ADR、更新版本范围和 golden, 重新评审。

Pigsty 内容按 4.4 文档在 2026-07-29 核验。扩展目录、包版本和支持矩阵会 持续变化,实际变更前必须查目标 Pigsty 版本与仓库。

权威入口:

本章明确区分三种陈述:

  1. PostgreSQL/Pigsty 文档定义的机制;
  2. 本章针对三个问题作出的架构选择;
  3. 当前本地 evidence 实际证明的观察。

只有第三类能由 /tmp/pg36-ch14-final 或读者自己的 evidence 目录直接 复现。


上一章:言出法随:函数、触发器与存储过程 · 返回上卷导读 · 下一章:见微知著:全文、模糊与向量检索 · 查看全书目录 · 查看索引中心

14.1 PostgreSQL 扩展机制

CREATE EXTENSION vector; 只有一行,却横跨文件系统、权限系统、版本图和 数据库依赖。要治理扩展,必须先把这一行展开。

本节讨论 PostgreSQL 自己知道什么、不会替你知道什么。软件包仓库、容器与 Pigsty 映射留到后面。

14.1.1 control、SQL 脚本、动态库与对象所有权

一套扩展至少有两个身份

假设执行:

CREATE EXTENSION vector
WITH SCHEMA app_ext
VERSION '0.8.4';

这里的 vectorSQL 扩展名。它不是项目名 pgvector,也不必等于 RPM/DEB 包名。PostgreSQL 根据 server 自己的安装目录寻找:

$(pg_config --sharedir)/extension/vector.control
$(pg_config --sharedir)/extension/vector--0.8.4.sql
$(pg_config --pkglibdir)/vector.so       # Linux 常见
$(pg_config --pkglibdir)/vector.dylib    # macOS 可能出现

典型扩展由三类文件组成:

文件 作用 PostgreSQL 何时使用
name.control 元数据、默认版本、权限、依赖、可迁移性 发现与创建/更新扩展
name--version.sql 创建这一数据库版本的成员对象 CREATE EXTENSION
name--old--new.sql 从一个对象版本迁到另一个版本 ALTER EXTENSION UPDATE
动态库 C 函数、hook、访问方法等运行代码 创建时或 backend 加载/调用时

纯 SQL 扩展可以没有动态库;只提供可加载模块的组件也可能没有 CREATE EXTENSION 接口。不能从扩展名猜文件组合,要读 control 与目标版本 说明。

查看当前 server 的查找位置:

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

这里的 pg_config 必须属于目标 server major。用 PATH 中另一个 PostgreSQL 版本的 pg_config 检查文件,可能得到一个完全正确、却与正在运行实例无关 的目录。

control 文件是创建合同

一个简化 control 文件可能表达:

comment = 'example data type'
default_version = '1.4'
module_pathname = '$libdir/example'
relocatable = true
superuser = true
trusted = false
requires = 'btree_gist'

关键字段:

  • default_version:未写 VERSION 时创建哪个对象版本;
  • module_pathname:版本 SQL 中 MODULE_PATHNAME 的替换值,常指向 $libdir 下动态库;
  • requires:必须先存在的其他扩展;
  • superuser:安装脚本是否原则上要求超级用户;
  • trusted:在 superuser=true 的前提下,是否允许具备当前数据库 CREATE 权限的非超级用户安装;
  • relocatable:扩展整体是否允许换 schema;
  • schema:若指定,强制成员安装到该 schema,并使扩展不可随意迁移。

这些是 具体已安装 control 文件 的事实,不是扩展项目永久不变的属性。 同名扩展升级后可以改变元数据,发行版也可能带不同补丁。用目录视图读取 当前 server 实际看到的值:

SELECT
    name,
    version,
    installed,
    superuser,
    trusted,
    relocatable,
    schema,
    requires
FROM pg_available_extension_versions
WHERE name IN ('pg_trgm', 'vector')
ORDER BY name, version;

本章正式夹具观测到:

pg_trgm 1.3..1.6  superuser=t trusted=t relocatable=t
vector  0.8.4     superuser=t trusted=f relocatable=t

这是 PostgreSQL 18.6 + 当前本地支持文件的证据;读者环境必须重查。

CREATE EXTENSION 创建的是一个依赖边界

版本 SQL 可以创建类型、函数、操作符、访问方法、opclass、表或其他对象。 PostgreSQL 把这些对象登记为扩展成员。扩展本身记录在:

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

成员关系记录在 pg_depend,依赖类型为 e

SELECT
    d.classid::regclass AS member_catalog,
    count(*) AS members
FROM pg_extension AS e
JOIN pg_depend AS d
  ON d.refclassid = 'pg_extension'::regclass
 AND d.refobjid = e.oid
 AND d.deptype = 'e'
WHERE e.extname = 'pg_trgm'
GROUP BY d.classid
ORDER BY member_catalog::text;

这个关系有三个后果:

  1. 成员通常不能绕过扩展被单独删除;
  2. DROP EXTENSION 会删除成员,即使没有写 CASCADE
  3. pg_dump 通常用 CREATE EXTENSION 重建整组对象,而不是逐个 dump 成员定义。

第三点非常重要:备份文件能记录“需要 vector 0.8.4 的对象合同”,却不会 把 control、SQL 脚本和动态库塞进备份。恢复目标必须先拥有兼容支持文件。

nominal schema 不是容器

pg_extension.extnamespace 常被叫作扩展 schema,但它不是一个不可穿透的 容器。官方文档明确指出,扩展成员可能位于多个 schema;这个字段表示扩展 的 nominal schema。扩展名本身也不受 schema 限定:

-- 一个数据库中只能有一个同名 extension
CREATE EXTENSION pg_trgm SCHEMA app_ext;

-- 这不是合法的第二份“另一个 schema 的 pg_trgm”
CREATE EXTENSION other.pg_trgm;

relocatable=true 也不表示所有业务依赖都能无痛迁移。换 schema 会影响:

  • 未全限定的函数与操作符解析;
  • 固化在 view、expression index 或 function body 中的对象引用;
  • 应用 search_path
  • dump/restore 与旧迁移脚本;
  • 安全审计假设。

把“目录允许 ALTER EXTENSION ... SET SCHEMA”与“应用兼容迁移”分开验证。

扩展 owner 与成员 owner

扩展有自己的 owner。通常创建者成为 extension owner;但安装脚本内部成员 的所有权还受脚本、可信安装规则和 PostgreSQL 版本语义影响,不能简单假设 “schema owner 拥有其中一切”。

本章把 pg_trgm 装在 shop_ch14

SET ROLE pg36_owner;
CREATE EXTENSION pg_trgm
  WITH SCHEMA shop_ch14
  VERSION '1.3';
RESET ROLE;

由于本地 control 标记 trusted=true,有数据库 CREATE 权限的 pg36_owner 可以安装,extension owner 是 pg36_owner。安装脚本在受控 的高权限上下文中执行,使扩展能够创建所需对象。

这不是把任意第三方 SQL 交给普通用户安全执行。trusted 是扩展供应者和 发行者作出的安全承诺,管理员仍要审查来源、版本与安装 schema。

参见 Packaging Related Objects into an Extensionpg_extension

14.1.2 普通扩展、预加载库与超级用户需求

“已创建”不等于“已加载”

按运行方式可以粗分四类:

类型 支持文件 CREATE EXTENSION preload/restart
纯 SQL 扩展 control + SQL 通常需要 通常不需要
按需加载的 C 扩展 control + SQL + library 通常需要 首次调用可加载
需要早期 hook 的扩展 control + SQL + library 通常需要 常需 shared preload
非 SQL 插件 library 或可执行组件 可能不需要 按子系统规则使用

扩展若要在 backend 初始化早期注册 shared memory、planner/executor hook、 background worker 或全局审计能力,往往需要:

shared_preload_libraries = '...'

这个参数在 server 启动时处理。修改后 reload 不够,通常需要滚动重启或 集群重启。库名拼错、文件缺失或二进制不兼容,可能直接阻止实例启动,所以 它是比普通 CREATE EXTENSION 更高风险的变更。

检查声明与 live 值:

SELECT
    name,
    setting,
    source,
    sourcefile,
    pending_restart
FROM pg_settings
WHERE name = 'shared_preload_libraries';

只看配置仓库中的 YAML 不够;只看 SHOW 也不够。前者是意图,后者是当前 实例事实,还要在所有主备节点检查动态库。

pg_trgm 与本章的 vector 0.8.4 不需要 shared preload。不能由此推断 其他版本或扩展也不需要。Pigsty 当前文档列举 Citus、TimescaleDB、 pg_cronpgaudit 等常见预加载场景,最终以目标扩展/版本文档和启动 实验为准。

superusertrusted 是两道判断

pg_available_extension_versions 中:

superuser=true, trusted=false

表示只有超级用户可以执行创建/更新脚本。vector 在本章环境属于这一类:

SET ROLE pg36_owner;
CREATE EXTENSION vector
  WITH SCHEMA shop_ch14
  VERSION '0.8.4';

得到:

SQLSTATE 42501
permission denied to create extension "vector"
HINT: Must be superuser to create this extension.

管理员安装后:

CREATE EXTENSION vector
  WITH SCHEMA shop_ch14
  VERSION '0.8.4';

extension owner 保持管理员角色。应用只获得使用所需类型、函数和表权限, 不获得扩展所有权。

对:

superuser=true, trusted=true

有数据库 CREATE 权限的非超级用户可以安装。安全关键点是:

  • 安装脚本以 bootstrap superuser 的能力执行;
  • extension owner 是调用者;
  • 供应者必须保证非特权调用者不能借脚本选择、schema 或预置对象提权;
  • 管理员应只信任随受控 PostgreSQL 发行版交付、且明确标记 trusted 的版本。

这就是为什么官方 CREATE EXTENSION 文档警告:从不可信来源安装扩展,相当 于以高权限运行其安装脚本。版本 SQL 能执行 DDL,也可能引用安装 schema 中预先存在的对象;安全安装应使用受控 schema 和受控 search_path

权限分层

推荐把角色拆开:

platform/admin
  ├─ installs OS packages on every node
  ├─ changes preload and restarts
  ├─ creates untrusted extensions
  └─ owns privileged extension lifecycle

NOLOGIN database owner
  ├─ owns application schemas/tables
  ├─ may own reviewed trusted extensions
  └─ runs migrations through controlled SET ROLE

application login
  ├─ uses selected functions/operators/types
  └─ cannot CREATE/ALTER/DROP EXTENSION

本章证明:

-- pg36_app
ALTER EXTENSION pg_trgm UPDATE TO '1.6';

返回:

SQLSTATE 42501
must be owner of extension pg_trgm

应用能使用扩展能力,并不需要拥有生命周期控制权。

创建失败要按层定位

常见错误及第一检查点:

错误 更可能是哪一层 首查
extension is not available control 文件不可见 pg_available_extensions、server sharedir
could not access file $libdir/... 动态库缺失/路径错 pg_config --pkglibdir、各节点文件
must be superuser control 权限合同 pg_available_extension_versions
must be loaded via shared_preload_libraries 进程初始化条件 pg_settings、重启状态、日志
extension already exists 数据库作用域冲突 pg_extension
no installation script for version 支持文件/版本图不完整 available versions、包版本
incompatible library server major/CPU/ABI 错配 包构建、PG major、架构、启动日志

不要用反复 CREATE EXTENSION ... CASCADE 猜答案。先把错误归到供应、进程或 数据库层。

14.1.3 扩展依赖、版本与 ALTER EXTENSION

requires 是扩展依赖,不是 OS 包依赖

control 中:

requires = 'foo, bar'

表示数据库内扩展依赖。创建前可以显式安装:

CREATE EXTENSION foo;
CREATE EXTENSION bar;
CREATE EXTENSION target;

也可以:

CREATE EXTENSION target CASCADE;

但生产治理不应默认 CASCADE,因为它会:

  • 选择依赖的默认版本;
  • 使用当前 schema/search path 决定安装位置;
  • 扩大实际变更集合;
  • 让审批只写一个扩展,实际多装若干对象。

更可审计的做法是显式列出依赖顺序、版本、schema、owner 和每步验证。

数据库依赖也不替代 OS 包依赖。目标 control 文件声明需要 foo,但节点上 仍必须先安装提供 foo.control、SQL 和 library 的软件包。

软件支持版本与对象版本分离

假设节点刚装入支持 1.6 的新包,而数据库仍显示:

SELECT extversion
FROM pg_extension
WHERE extname = 'pg_trgm';

-- 1.3

此时:

filesystem supports: 1.3, 1.4, 1.5, 1.6
database objects are: 1.3

这是正常的中间状态,不是 PostgreSQL 自动遗漏升级。装包不会主动在每个 数据库执行对象迁移;管理员必须逐库评审:

ALTER EXTENSION pg_trgm UPDATE TO '1.6';

先列出版本:

SELECT
    name,
    version,
    installed,
    superuser,
    trusted,
    relocatable
FROM pg_available_extension_versions
WHERE name = 'pg_trgm'
ORDER BY string_to_array(version, '.')::integer[];

再检查更新图:

SELECT source, target, path
FROM pg_extension_update_paths('pg_trgm')
WHERE source = '1.3'
   OR target = '1.6'
ORDER BY source, target;

本章得到:

1.3 -> 1.6 : 1.3--1.4--1.5--1.6

PostgreSQL 会按可用更新脚本寻找路径;路径不是任意版本号比较。若没有从 当前对象版本到目标版本的脚本链,更新就不能发生。所谓“降级”同样需要明确 反向脚本,不能假设 ALTER EXTENSION ... TO old 会还原。

更新是 DDL 事务,不是无风险元数据改名

更新脚本可执行 DDL/DML,可能:

  • 替换函数、操作符与类型支持函数;
  • 增删成员;
  • 改写扩展配置表;
  • 获取对象锁;
  • 使依赖表达式、索引或 cached plan 失效;
  • 对大表触发长时间工作。

扩展脚本在一个隐式事务中运行,不能在其中自行提交,也不能把需事务外执行 的操作当普通更新步骤。即使脚本通常很快,也要把它当 schema migration:

freeze target package build
  -> read release notes and update scripts
  -> clone/restore rehearsal
  -> dependency and lock inspection
  -> behavior baseline
  -> ALTER EXTENSION UPDATE
  -> catalog + query + plan + log verification
  -> observation window

更新 owner 才能 ALTER EXTENSION;某些脚本还因 control 权限需要更高 特权。本章由 pg36_owner 更新自己拥有的 trusted pg_trgm,而不是给 应用角色临时提权。

成员变化与 dump 语义

扩展维护者可以用:

ALTER EXTENSION name ADD object;
ALTER EXTENSION name DROP object;

调整成员关系。这不是业务迁移的日常捷径。成员一旦归入扩展:

  • dump 通常不再单独保存其定义;
  • DROP EXTENSION 会删除它;
  • 随手修改成员定义可能不会按预期进入 dump;
  • 正确升级应通过新的扩展版本和 update script 交付。

检查成员,而不是只看 \dx

SELECT
    e.extname,
    d.classid::regclass AS catalog,
    count(*) AS members
FROM pg_extension AS e
JOIN pg_depend AS d
  ON d.refclassid = 'pg_extension'::regclass
 AND d.refobjid = e.oid
 AND d.deptype = 'e'
GROUP BY e.extname, d.classid
ORDER BY e.extname, catalog::text;

本章 pg_trgm 1.3 有 37 个登记成员,更新到 1.6 后为 47;vector 0.8.4 在 PostgreSQL 18.6 夹具中有 237 个。成员数量用于发现本次环境漂移, 不是跨 PG major 的普适 golden。

本节结论

CREATE EXTENSION 读成一条完整声明:

using support files from this exact server installation,
run this reviewed version script with this privilege model,
create one database-scoped extension owned by this role,
attach these member objects and dependencies,
and promise that future dump/restore/update can obtain matching files.

少掉任何一段,扩展都只是“今天在这台主机上能用”,还不是可运营能力。


返回本章目录 · 下一节:内核、发行版与托管服务 · 查看全书目录 · 查看索引中心

14.2 内核、发行版与托管服务

“基于 PostgreSQL”可能表示使用上游源码加少量补丁,也可能只表示接受一部分 PostgreSQL wire protocol。两者对扩展的意义完全不同。

选扩展前先冻结运行载体:

server implementation
  + server major/minor/build
  + OS/distribution/architecture
  + package source and build
  + topology and managed restrictions
  + database extension object version

扩展不是脱离这些条件存在的功能标签。

14.2.1 上游 PostgreSQL、补丁内核与兼容性承诺

“内核”至少要说明源码与构建

在本书中,上游 PostgreSQL 指 PostgreSQL Global Development Group 发布的 代码与版本语义。供应者可以在其上:

  • 回移安全或缺陷补丁;
  • 增加认证、存储、优化器或复制能力;
  • 替换某些系统组件;
  • 发布自己的包名、构建号与支持周期;
  • 形成需要独立升级路径的 fork。

“补丁少”不自动等于二进制兼容;“版本号相同”也不证明动态库来自相同 ABI 与编译选项。C 扩展会与 server headers、符号、内存上下文、catalog 和内部 API 交互。PostgreSQL 不承诺跨 major 的内部 C ABI,扩展通常必须按目标 major 构建。

先采原始身份:

SELECT version();
SHOW server_version;
SHOW server_version_num;

SELECT
    name,
    setting,
    source
FROM pg_settings
WHERE name IN (
    'server_version',
    'server_version_num',
    'data_directory',
    'config_file',
    'shared_preload_libraries'
);

再采主机/包事实:

postgres --version
pg_config --version
pg_config --configure
uname -m

在 Pigsty 环境还要记录 pg_versionpg_mode、镜像/仓库快照与节点实际包 清单。不要只复制应用连接返回的 version();代理、兼容层或读写路由可能让 它不足以唯一标识整个集群。

扩展兼容承诺要逐项问

对一个补丁内核或 fork,至少问:

问题 为什么影响扩展
是否使用上游系统目录布局 扩展 SQL 可能查询/修改 catalog
是否支持 PGXS 与上游 server headers 决定能否按目标内核构建
是否保持所需 C symbols/hook 决定动态库能否加载和正确运行
WAL、存储和复制是否改动 自定义类型/访问方法能否在备库恢复
pg_upgrade 是否使用上游路径 外部模块与数据格式怎样迁移
由谁发布扩展包 上游扩展 release 不等于目标内核构建
谁承担联合支持 内核供应者和扩展供应者是否互相认可组合

“这个扩展支持 PostgreSQL 18”只说明扩展项目的一个范围;目标若是 PostgreSQL 18 衍生内核,仍需该组合的构建与验证证据。

SQL 扩展也不必然可移植

没有 C 动态库只能降低 ABI 风险,不能消除语义耦合。纯 SQL 扩展可能依赖:

  • 特定系统目录列;
  • 特定函数、数据类型或语法版本;
  • planner 行为;
  • event trigger;
  • trusted extension 机制;
  • superuser/owner 权限;
  • 复制、dump 或安全策略。

因此兼容性不是“C 扩展危险、SQL 扩展安全”的二分,而是依赖面的大小。

支持矩阵要精确到组合

不要写:

supports PostgreSQL

而写:

server: upstream PostgreSQL 18.6, vendor build X
OS: Ubuntu 24.04 amd64
extension package: pgvector build Y
database object: vector 0.8.4
topology: 1 primary + 2 physical standbys
preload: not required
backup/restore: rehearsed on clean target Z

同一扩展在 EL9/aarch64、Ubuntu/amd64 和某托管服务上是三个验证组合。

14.2.2 包仓库、容器镜像与托管白名单

包仓库解决供应,不替你做数据库升级

发行版包通常编码:

extension project version
PostgreSQL major
OS family/version
CPU architecture
vendor release/build

例如 Pigsty 的包别名 pgvector 可以映射到不同系统上的:

EL:     pgvector_18*
Debian: postgresql-18-pgvector

这层映射很有价值,但别名不是 SQL 名:

pg_extensions: [pgvector]   # package intent

数据库中仍是:

CREATE EXTENSION vector;    -- SQL extension name

仓库有包只证明“某个源声明可以供应”;还要验证:

  • 目标 OS/PG major/架构是否有具体 artifact;
  • repo metadata、签名与校验是否可信;
  • 包是否已经同步到本地/离线仓库;
  • 所有节点安装的 build 是否相同;
  • control、更新 SQL 和动态库是否都随包出现;
  • 升级包后每个数据库的 extversion 是否仍需迁移。

锁定生产变更时,保存具体包 NEVRA/DEB version 或文件哈希,不只保存一个 会随仓库漂移的“latest”别名。

容器把供应快照化,但不把数据生命周期一起快照

容器镜像可以把 server 与扩展文件打包在同一 digest 中:

image digest
  ├─ postgres binary
  ├─ control and SQL scripts
  └─ shared libraries

这有助于节点一致性,却有几个陷阱:

  1. 数据目录通常在持久卷,里面的 pg_extension.extversion 不随镜像自动 更新;
  2. 滚动换镜像期间,新旧 pod 可能同时服务,动态库 build 必须满足复制和 failover 条件;
  3. 恢复 job、备份验证 job 和临时维护容器也需要相同扩展文件;
  4. 镜像能启动不代表 ALTER EXTENSION UPDATE 已完成;
  5. 使用浮动 tag 会把可复现优势重新丢掉。

因此镜像 digest 是供应锁,不是数据库迁移状态。

托管白名单是产品合同

托管 PostgreSQL 常限制超级用户、文件系统和 server 参数。用户通常只能从 服务商允许列表中执行:

CREATE EXTENSION approved_name;

这带来不同问题:

  • 是否允许该扩展;
  • 允许哪个对象版本;
  • 哪些区域、实例规格或 PG major 可用;
  • 是否需要服务商参数组/重启;
  • 谁控制更新窗口;
  • 是否暴露扩展 owner;
  • 是否允许自定义 schema;
  • 备份、只读副本、跨区恢复与 major upgrade 是否支持;
  • 从服务迁出时怎样导出自定义类型数据。

托管服务显示“支持 pgvector”,仍不能直接套用自建包的版本、参数和升级 步骤。白名单名称相同,控制面合同可能不同。

仓库、镜像与白名单的共同锁文件

为每个环境维护:

server:
  implementation: upstream-postgresql
  version: 18.6
  build: vendor-build-id
  os: ubuntu-24.04-amd64

extension:
  sql_name: vector
  project: pgvector
  package_alias: pgvector
  package_version: exact-build
  object_version: 0.8.4
  preload: false

supply:
  repo_snapshot_or_image_digest: immutable-id
  control_sha256: ...
  install_sql_sha256: ...
  library_sha256: ...

validation:
  primary: passed
  standbys: passed
  clean_restore: passed
  major_upgrade_clone: passed

不是所有项目都要手写 YAML,但这些字段必须能从 CMDB、inventory、镜像 SBOM、evidence 或变更单还原。

14.2.3 “兼容 PostgreSQL”需要逐层验证

五层兼容模型

把“兼容”拆为五层:

要验证什么 典型误判
SQL 语义 类型、函数、事务、隔离、DDL 行为 能跑简单 CRUD 就等于 PostgreSQL
Wire protocol 驱动连接、认证、参数、错误字段 驱动能连就等于 server 等价
Catalog/API pg_catalog、扩展机制、统计视图 ORM 能用就等于管理工具能用
Extension control/SQL/C ABI、preload、成员与版本 “支持 pgvector”就等于任意版本
Operations 备份、PITR、复制、failover、upgrade、监控 单实例功能测试代替生产生命周期

兼容声明必须说明通过了哪层。一个 wire-compatible 服务可能不提供 CREATE EXTENSION;一个支持扩展 SQL 接口的服务可能不允许用户控制版本; 一个上游二进制兼容内核仍可能在备份或升级控制面上有不同合同。

用需求驱动 probe

不要为了“全面”跑一堆无关 SQL。根据应用依赖形成最小 probe:

application contract
  ├─ exact type/function/operator signatures
  ├─ SQLSTATE and transaction behavior
  ├─ planner/index behavior
  ├─ privilege boundary
  ├─ backup/restore representation
  └─ failover/upgrade behavior

本章对 pg_trgm/vector 的 probe 包括:

-- 供应可见性
SELECT * FROM pg_available_extension_versions
WHERE name IN ('pg_trgm', 'vector');

-- 数据库对象
SELECT * FROM pg_extension
WHERE extname IN ('pg_trgm', 'vector');

-- 成员关系
SELECT ... FROM pg_depend WHERE deptype = 'e';

-- 索引实现
SELECT ... FROM pg_index JOIN pg_am JOIN pg_opclass ...;

-- 权限失败
ALTER EXTENSION pg_trgm UPDATE TO '1.6'; -- application: 42501

-- 行为与计划
EXPLAIN ... title % 'PostgreSQL extenson';
EXPLAIN ... ORDER BY embedding <-> '[1,0,0]';

这些 probe 仍没有覆盖备库与恢复,所以 evidence 不能写“生产兼容已验证”。

区分等价、适配与迁移

三个词不要混用:

  • 等价:在声明范围内行为相同;
  • 适配:应用通过条件分支、兼容层或限制使用范围后可运行;
  • 迁移:接受行为变化并修改 schema、查询、运维或 SLO。

例如目标不支持 HNSW,但支持精确向量距离:

不是:完全兼容 pgvector
可能是:类型/距离查询兼容,ANN 索引不兼容
决策是:小数据集适配,或迁移到另一检索架构

精确描述可以阻止“兼容”在采购、开发和事故处理中不断膨胀。

兼容矩阵

对候选平台填表:

验证项 上游自建 Pigsty L1 托管候选 证据
SQL 扩展名/版本 catalog
package/build 服务商托管 package/API
preload/restart live setting
owner/权限 negative test
GIN/HNSW catalog + plan
物理副本 failover test
schema-only dump dump artifact
clean restore restore report
major upgrade cloned rehearsal
portable export row/checksum

空格不是“默认通过”,而是未验证。若某项与业务无关,可以标 N/A 并说明 理由;不能把它悄悄留空后宣称全兼容。

停止线

遇到以下任一情况,不进入生产:

  • 无法唯一标识 server/扩展 build;
  • 主备节点供应状态不一致;
  • 只有创建成功,没有 clean restore;
  • 目标服务商不能说明 major upgrade 时怎样处理扩展;
  • 自定义类型无法导出为稳定交换格式;
  • 兼容层不返回应用依赖的 SQLSTATE/事务语义;
  • 供应者与扩展项目相互否认联合支持;
  • 只能使用浮动包/tag,无法复现已测组合。

本节结论

“PostgreSQL 兼容”不是布尔值,而是一个带版本、层次和证据的向量:

compatibility =
  SQL × protocol × catalog × extension × operations
  under exact version/build/topology constraints

扩展越深入类型、索引、hook 与存储,越不能只验证前两层。


上一节:PostgreSQL 扩展机制 · 返回本章目录 · 下一节:扩展选型的六个问题 · 查看全书目录 · 查看索引中心

14.3 扩展选型的六个问题

扩展评审最容易从产品介绍开始:

它支持什么?

更好的起点是:

我们已经观察到什么问题?

本节用六个问题形成漏斗:

  1. 具体问题和原生替代是什么?
  2. 成功、停止与反例怎样测量?
  3. 是否引入数据格式锁定,怎样导出退出?
  4. 备份、复制与升级生命周期是否成立?
  5. 维护、许可证与商业连续性如何?
  6. 权限、崩溃面和供应链风险能否接受?

前两问证明价值,中间两问证明可运营,后两问证明风险归属。任何一问没有 答案,都只能进入调查或限域试点,不能直接成为平台默认。

14.3.1 它解决的具体问题和原生替代是什么

问题必须可证伪

以下不是问题陈述:

我们需要向量数据库。
我们需要分布式 PostgreSQL。
大家都在用时序扩展。
这个扩展会让查询更快。

它们已经把候选解写进需求。可评审的陈述应包含:

workload + current evidence + target + boundary

例如:

商品标题查询中,8% 的零结果请求只有一个拉丁字母拼写错误;
在 2000 万活跃标题、P95 50 ms 的边界内,希望返回至多 20 个候选;
中文分词、语义搜索和全站文档检索不在本次范围。

这时 pg_trgm 才是候选之一,而不是需求本身。

先列 PostgreSQL 原生替代

“原生”不等于永远更好,但它通常具有更小供应面。按问题检查:

问题 先检查
精确/前缀查找 B-tree、表达式/partial index、规范化列
词项全文检索 tsvector、GIN、词典与查询函数
范围/包含/重叠 range/multirange、GiST、EXCLUDE
半结构化属性 jsonb + GIN/表达式索引,或重新建模
地理点/简单距离 内置 point 是否真的足够;复杂 GIS 再评 PostGIS
时间分区/归档 declarative partitioning、维护流程
容量问题 查询/索引修正、归档、分区、纵向扩容
批量分析 物化、并行查询、专用副本或外部分析系统

还要列应用/外部服务替代。一个扩展减少网络跳数,但把失败和升级绑定到 PostgreSQL;外部服务增加分布式复杂性,却可能提供独立扩缩容与专用算法。 这是工程权衡,不是“数据库内一定更快”。

比较单位是完整方案

不要比较:

one SQL function vs one HTTP call

而比较:

PostgreSQL extension solution
  package + preload + schema + index + backup + standby + upgrade + skills

external service solution
  service + network + sync pipeline + consistency + backup + operations

native solution
  schema/query/index + application behavior + operational limits

遗漏生命周期成本,会让扩展看起来永远最简单;遗漏外部同步成本,又会让 独立服务看起来永远更可扩展。

第二问:成功与停止怎样测

一个 PoC 至少同时有:

  • 正确性/质量:结果集合、不变量、召回/精度或误差;
  • 性能:P50/P95/P99、吞吐、build time、写放大;
  • 资源:内存、磁盘、CPU、WAL、临时文件;
  • 运行:备库延迟、恢复时间、升级锁、失败表现;
  • 边界:数据规模、过滤选择性、并发、语言/模型/维度;
  • 停止线:何时立即拒绝或回到替代方案。

本章五行 fixture 的成功标准故意很窄:

pg_trgm: top ids 1,5,2 and GIN plan is usable
vector:  top ids 1,2,5 and HNSW plan is usable

它证明 API 与索引机制,不证明:

真实搜索质量
大规模 ANN recall
生产尾延迟
写入与索引构建成本
备库和恢复 SLO

因此 pg_trgm 的接受范围只是“有界单字段模糊匹配”;vector 仍是 pilot。

反例必须进入数据集

只测成功样本会让任何扩展通过。检索候选至少加入:

  • 短字符串、空值、重复值;
  • 不同语言、大小写、重音和规范化;
  • 高频词、低选择性谓词;
  • 过滤后很少/很多候选;
  • 大批更新与删除;
  • 冷缓存、热缓存;
  • 与业务谓词组合的真实查询。

分布式候选则要加入:

  • 跨分片事务;
  • 热分片;
  • rebalance;
  • 节点失联;
  • DDL 传播;
  • 全局唯一性与引用完整性;
  • 备份、恢复和扩缩容窗口。

问题没有对应反例,PoC 更像演示。

14.3.2 数据格式是否锁定、能否导出和退出

第三问:锁定发生在哪里

扩展可能只增加可重建索引,也可能让业务列使用自定义类型:

依赖 锁定程度 退出方式
纯函数、无持久数据 改查询后删除
可重建 expression/index/opclass 较低 先换查询/索引,再删除
extension-owned 配置表 导出、转换、重建
自定义列类型 列级转换或交换格式迁移
自定义 table/access method 全表重写/逻辑迁移
存储/WAL/分布式元数据 很高 专用迁移与拓扑退场

pg_trgm 在本章只提供函数、操作符与 GIN opclass,业务 title 仍是 text。退出可以先改查询、删除 GIN,再删除扩展。

vector 让:

embedding shop_ch14.vector(3)

成为列类型。只要该列存在:

DROP EXTENSION vector;

就会因依赖失败;使用 CASCADE 会把业务对象一起删除,不是可接受的退出。

在采用前写出出口

本章先建立可移植导出:

COPY (
    SELECT
        doc_id,
        title,
        embedding::text AS embedding_text
    FROM shop_ch14.candidate_doc
    ORDER BY doc_id
) TO STDOUT WITH (FORMAT csv, HEADER true);

输出形如:

1,PostgreSQL extension guide,"[1,0,0]"

这只建立一个交换入口。真正退出还要回答:

  • 文本格式由谁解析,精度是否损失;
  • 行数、主键和 checksum 怎样核对;
  • 目标类型是什么;
  • 应用何时双读/双写;
  • ANN 索引何时停止使用;
  • 大表转换是否重写、锁多久、产生多少 WAL;
  • rollback 点在哪里;
  • 备份中最后一个扩展依赖何时消失。

没有跑过迁移的“理论可导出”只能算风险缓解,不能算完成退出演练。

不把逻辑 dump 当数据出口

全库 pg_dump 通常写:

CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA shop_ch14;

这仍要求恢复端安装 vector。它是 同构恢复 合同,不是脱离扩展的出口。

真正的 portability artifact 应使用目标系统可理解的格式:

  • 内置 text/numeric/jsonb/array
  • CSV/JSON/Parquet 等交换格式;
  • 明确坐标系、单位、模型与版本的领域格式;
  • 行数、范围和 checksum。

例如 PostGIS 几何不能只导出一个没有 SRID 的坐标字符串;embedding 不能 只导出数字而丢掉 model、dimension、normalization 与 distance metric。

第四问:生命周期是否成立

锁定不只发生在数据格式,也发生在运维路径。逐项问:

install:
  every primary/standby/restore host?

backup:
  pg_dump and physical backup prerequisites?

restore:
  clean environment package bootstrap order?

replication:
  physical library parity?
  logical subscriber type/schema parity?

upgrade:
  extension object update path?
  server-major-compatible binary?
  lock and downtime?

rollback:
  package rollback?
  object downgrade script?
  data format backward compatibility?

只有 happy-path CREATE EXTENSION 的项目,生命周期证据为零。

退出预算

把退出成本量化:

项目 估算
需要转换的数据量 bytes / rows
双写窗口 hours/days
额外存储 old + new + indexes
最大锁窗口 seconds/minutes
WAL 与备库延迟 projected/tested
应用版本跨度 N / N+1 compatibility
回滚最晚点 before/after backfill/switch
人员与演练时间 owner + date

如果退出成本已经超过系统可承受窗口,决策不是“以后再说”,而是当前已经 形成实质锁定,必须由业务负责人接受。

14.3.3 维护活跃度、许可证与商业连续性

第五问不是“最近有没有 commit”

维护健康至少包括:

  • 是否有明确维护者和 release 流程;
  • 对当前/未来 PostgreSQL major 的响应速度;
  • 缺陷、崩溃和安全问题是否被分类与修复;
  • release notes 与 update scripts 是否完整;
  • CI 是否覆盖目标 OS/架构/PG major;
  • 文档是否说明备份、升级、preload 和限制;
  • issue/PR 是否有持续 triage;
  • 是否存在多名可发布维护者;
  • 旧版本支持与 EOL 策略是否清晰。

“每天很多 commit”可能只是功能开发;“半年没 commit”也可能是成熟稳定。 用与你的风险相关的证据判断。

许可证检查三个层次

至少分别检查:

  1. 源码许可证;
  2. 二进制包与捆绑依赖;
  3. 企业使用、再分发、托管服务或商业功能条款。

不要从项目名称、GitHub 页面徽章或旧博客推断。保存目标版本的 LICENSE、 NOTICE、依赖清单与法务结论。许可证可能随 major、模块或商业发行版变化。

技术上能装,不表示组织有权按计划分发;开源,也不表示所有附加服务和 品牌条款相同。

商业连续性不是“有公司背书”

公司支持可以降低某些风险,也引入:

  • 定价或授权变化;
  • 产品方向与开源版分叉;
  • 单一 vendor build;
  • 支持合同终止;
  • 收购、停服或仓库下线。

社区项目则可能有 bus factor、发布带宽和联合支持问题。两者都要问:

如果主要供应者明天停止交付,
我们能否合法获得源码、复现构建、修补安全问题、
恢复历史备份并迁出数据?

答案不一定要求团队自己维护 fork,但必须有时间与责任人。

建立维护快照

ADR 中保存带日期的证据:

project_release: exact-tag
reviewed_at: 2026-07-29
supported_pg: [14, 15, 16, 17, 18]
target_build: exact-package
license_review: ticket-or-document
security_contact: ...
last_restore_test: ...
next_review: ...

维护状态会变化,所以结论必须有复审日,不能把一次评估写成永久事实。

14.3.4 权限、崩溃面与供应链风险

第六问:谁获得什么能力

扩展评审画出特权链:

repository maintainer
  -> package builder
  -> node installer (root)
  -> PostgreSQL admin
  -> extension owner
  -> schema/object owner
  -> application users

每一环都可能改变下一环执行的代码。要记录:

  • 谁能把包加入仓库;
  • 谁能改 shared_preload_libraries 和重启;
  • 谁能 CREATE/ALTER/DROP EXTENSION
  • extension owner 是谁;
  • 成员函数默认给 PUBLIC 什么权限;
  • 应用通过哪些 schema、函数、操作符和类型使用;
  • 谁能在安装 schema 预置同名对象影响脚本解析。

C 扩展与 server 共享故障域

C 扩展不是旁路微服务,它在 PostgreSQL 进程地址空间内运行。缺陷可能导致:

  • backend crash;
  • postmaster 重启其他 backend;
  • 内存破坏;
  • 错误结果或数据损坏;
  • 无限循环/资源耗尽;
  • 特权边界漏洞。

这不表示拒绝所有 C 扩展。PostgreSQL 大量核心能力也使用 C;结论是:

一个 in-process 扩展的审核与发布等级,应接近数据库 server 组件,而不是 普通 SQL 库。

需要:

  • 来源与构建可追溯;
  • 目标 major/架构测试;
  • crash/recovery 与备库测试;
  • 资源上限;
  • 安全通告与快速替换能力;
  • core dump/日志/回滚预案。

纯 SQL/PL 扩展没有任意 C 内存访问,但仍可能包含提权、search_path、 动态 SQL、错误 ACL、超大查询和对象劫持风险。

供应链不止校验下载文件

最小证据链:

upstream source/tag
  -> trusted build pipeline
  -> signed repository metadata
  -> exact OS package
  -> control/SQL/library hashes on nodes
  -> database extversion/member inventory

本章 package manifest 对这些具体文件做 SHA-256:

pg_trgm.control
pg_trgm--1.3.sql
pg_trgm--1.3--1.4.sql
pg_trgm--1.4--1.5.sql
pg_trgm--1.5--1.6.sql
pg_trgm shared library
vector.control
vector--0.8.4.sql
vector shared library

哈希能发现漂移,不能证明代码安全。它必须与仓库签名、构建来源、审计和漏洞 响应组合。

风险分级

一个实用起点:

等级 例子 最低门槛
L0 可重建 SQL/索引 不改变持久类型、无 preload 功能/计划/恢复/退出
L1 自定义类型/C 函数 持久列依赖动态库 供应锁、主备、clean restore、出口
L2 preload/hook/worker server 启动与全局执行路径 滚动重启、crash/failover、资源与禁用
L3 存储/分布式拓扑 WAL、shard、专用 catalog 完整故障模型、升级/回退、联合支持

等级不是产品好坏,而是证据成本。高等级候选可以采用,但不能用 L0 的 “创建成功”验收。

决策状态

只允许清晰状态:

investigate  资料不足
pilot        限域、有停止线、不得成为默认依赖
accept       在明确版本/场景内批准
reject       当前问题或风险不匹配
superseded   已由新 ADR 替代

避免“原则同意”“先装上再看”“有需要都可以用”这类无法执行的结论。

本节结论

六问的顺序很重要:

problem -> evidence -> data/exit -> lifecycle -> continuity -> security

越早失败,越应尽早停止。没有实际问题时,不需要花几周证明供应链;问题 成立后,也不能用性能收益跳过恢复和退出。


上一节:内核、发行版与托管服务 · 返回本章目录 · 下一节:生命周期与升级耦合 · 查看全书目录 · 查看索引中心

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 metadata pgvector 0.8.4
节点软件包 build rpm/deb/image vendor release + PG18 + OS
数据库对象版本 pg_extension.extversion vector 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 管理扩展可用性 · 查看全书目录 · 查看索引中心

14.5 用 Pigsty 管理扩展可用性

Pigsty 能把仓库、包、配置和数据库声明统一管理,但平台声明不是 PostgreSQL 事实的替代品。正确用法是:

declare intent in Pigsty
  -> converge nodes and databases
  -> verify live package/config/catalog/query evidence

本节以 Pigsty 4.5 文档为基线。参数与扩展目录会变化,目标集群变更前应使用 同版本文档和 inventory,而不是照抄本章时间点。

14.5.1 包、仓库、模板与节点差异

Pigsty 中的四步模型

当前 Pigsty 扩展文档把过程分成:

Download
  从上游仓库取得包,或同步到本地仓库

Install
  在 PGSQL cluster 的所有相关节点安装 OS 包

Configure
  处理 shared_preload_libraries 和扩展参数

Create
  在指定数据库执行 CREATE EXTENSION

这与上一节三层状态一致:

Pigsty 动作 主要对象 原生验证
Download repo/cache repo metadata、artifact、checksum
Install node package/files package inventory、control/library
Configure Patroni/PostgreSQL 参数 pg_settings、restart、日志
Create database object pg_extension、成员、查询

不是每个扩展都需要 preload,也不是每个已安装插件都需要创建数据库对象。

pg_packagespg_extensions

Pigsty 4.5 文档区分:

pg_packages:
  - pgsql-main pgsql-common

pg_extensions:
  - postgis timescaledb pgvector
  • pg_packages:通用基础包组,通常用于所有 cluster 的核心组件;
  • pg_extensions:特定 PGSQL cluster 需要的扩展软件包,初始化时安装, 也可对已存在 cluster 执行 pg_extension tag 收敛。

示例:

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
    10.10.10.11: { pg_seq: 2, pg_role: replica }
  vars:
    pg_cluster: pg-meta
    pg_extensions: [pgvector]

对于已运行 cluster,先修改受版本控制的声明,再执行目标明确的 playbook:

./pgsql.yml -l pg-meta -t pg_extension

临时命令行覆盖:

./pgsql.yml -l pg-meta -t pg_extension \
  -e '{"pg_extensions":["pgvector"]}'

适合受控应急或实验,但若不回写 inventory,下一位维护者看不到持久意图。

参见 Pigsty: Install Extensions

包别名是跨发行版映射

Pigsty 使用稳定别名:

pgvector
postgis
timescaledb

映射到 PG major 与 OS 对应包,例如:

pgvector
  -> pgvector_18*                 # EL
  -> postgresql-18-pgvector       # Debian/Ubuntu

还提供 pgsql-ragpgsql-gispgsql-fts 等类别别名。类别安装范围大, 实验便利不等于生产应一次装整类。生产 ADR 优先列精确候选,避免供应面无意 扩大。

包别名与 SQL 名必须在清单中同时保存:

项目 package alias SQL extension
pgvector pgvector vector
PostGIS postgis postgispostgis_topology
pg_trgm pgsql-main/默认包集合供应 pg_trgm

参见 Pigsty: Extension Package Aliases

默认供应与默认启用不是同一层

Pigsty 4.5 当前默认文档说明:

  • pgvector 随默认 pgsql-main 包集合安装;
  • pg_trgm 位于 pg_default_extensions,默认在数据库的 public schema 启用;
  • pg_stat_statementsauto_explain 等进入默认 preload/观测集合。

这些默认值会随 Pigsty release 演进。目标环境要检查自身 inventory,而不是 从“4.4 默认”反推一个已升级多次的 cluster。

还要注意:若 pg_trgm 已在数据库 public 创建,就不能再在 app_ext 创建第二份同名扩展。自定义 schema 前必须协调 pg_default_extensions,不能让两个声明互相竞争。

参见 Pigsty: Default Extensions

仓库可达性与本地仓库

在线环境可能直接从配置的上游/第三方仓库下载。受限环境通常由 Pigsty infra 节点维护本地软件仓库。无论哪种模式,验证:

inventory requests alias
  -> alias resolves for OS + PG major + architecture
  -> artifact exists in chosen repository snapshot
  -> every PG node installs same build
  -> future restore/new-node path can obtain it

“主库现在有文件”不能证明:

  • 新副本能加入;
  • 灾备站点能恢复;
  • 离线仓库仍保留旧版本;
  • major upgrade 目标包已经可用。

扩展变更单应附 repo snapshot/version,而不只附公共下载 URL。

参见 Pigsty: Download Extensions

节点漂移要按拓扑检查

至少覆盖:

primary
all synchronous/asynchronous standbys
delayed standby
disaster-recovery nodes
backup/restore worker image
future replacement node template

可以用 Pigsty/Ansible 采包事实:

ansible pg-meta -b -a 'pig ext status -c -v 18'

具体 pig 子命令以目标版本为准。更稳妥的检查还包括:

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

对 control/library 取 hash,结果按 host 保存。不要只看 play recap:

ok=...
changed=...

它说明自动化执行状态,不说明数据库能加载文件。

漂移矩阵

host role PG build package build control hash library hash preload live
pg-1 primary
pg-2 replica
pg-3 replica

任一 host 不同,先修供应层,不急着执行数据库 DDL。

14.5.2 声明安装与数据库内 CREATE EXTENSION

三个参数分别回答三个问题

pg_extensions: [pgvector]

pg_libs: 'pg_stat_statements, auto_explain'

pg_databases:
  - name: pg36_shop
    extensions:
      - { name: vector, schema: app_ext }

含义:

参数 问题
pg_extensions cluster 节点要安装哪些扩展软件包
pg_libs server 启动时要 preload 哪些库
pg_databases[].extensions 某数据库要创建哪些 SQL extension

三者不能互换:

  • vector 写进 pg_extensions 会把 SQL 名误作包别名;
  • 只写 pgvector package 不会自动保证每个已有数据库都创建对象;
  • 把不需 preload 的库塞进 pg_libs 会增加启动耦合;
  • 只执行 CREATE EXTENSION,备库节点可能仍缺包。

Pigsty 当前数据库声明示例:

pg_databases:
  - name: meta
    extensions:
      - { name: vector }
      - { name: postgis, schema: public }
      - { name: pg_stat_statements, schema: monitor }

这里用的是 SQL extension name。参见 Pigsty: Create Extensions

本章声明片段

pigsty-declaration.example.yml 刻意写成:

pg_extensions:
  - pgvector

pg_databases:
  - name: pg36_shop
    schemas:
      - { name: app_ext, owner: pg36_owner }
    extensions:
      - { name: vector, schema: app_ext }

它没有重复声明 pg_trgm,因为 stock Pigsty 默认已经在 public 启用。

本地直连实验为了让 namespace 与成员集中可见,把:

pg_trgm + vector -> shop_ch14

放在同一 schema。这是教学 fixture,不要求读者破坏 Pigsty 的合理默认。 平台实践可以是:

pg_trgm -> public (default)
vector  -> app_ext (per-database declaration)

只要 ADR、查询、dump 与权限证据反映真实布局。

声明不应夹带对象版本假设

仓库安装“最新可用包”与数据库对象 VERSION 是两个控制面。Pigsty 初始化 声明能创建扩展,但生产需要单独的 SQL migration:

CREATE EXTENSION vector
WITH SCHEMA app_ext
VERSION '0.8.4';

或:

ALTER EXTENSION vector UPDATE TO 'reviewed-version';

是否在 Pigsty YAML 固定 version 要以该版本参数 schema 和初始化实现为 准;即使声明能写版本,已有数据库升级仍应通过有证据的迁移流程,而不是 假设重新跑初始化会更新。

推荐职责:

Pigsty inventory:
  repository/package/preload/database intent

SQL migration repository:
  exact CREATE/ALTER statements
  owner/schema/ACL
  pre/post catalog and behavior assertions

evidence:
  node + live config + database facts

预加载变更的发布顺序

需要 preload 的扩展:

1. install package on every node
2. update pg_libs/parameters in inventory
3. apply Patroni/PostgreSQL config
4. rolling restart under HA policy
5. verify live setting and logs on every node
6. CREATE EXTENSION in intended databases
7. verify query/metrics/failover

CREATE EXTENSION 再补 preload 可能直接失败;先改 preload 而节点缺库 可能导致 restart 失败。

本章两项扩展不要求 preload,因此声明不应为了“统一”加入它们。最小配置面 也是可靠性。

owner 与 schema

Pigsty 能创建角色、数据库与 schema;扩展 migration 仍要检查最终 owner:

SELECT
    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;

对 untrusted extension,管理员创建后可能由高权限角色拥有。不要为了让 应用迁移工具“方便”而把 extension owner 交给 LOGIN 应用角色。

已有 cluster 的收敛

不要只编辑 inventory 后等下一次重建:

review declaration diff
  -> download/sync repository if needed
  -> run package convergence on exact cluster
  -> configure/restart if needed
  -> run database migration
  -> verify all layers
  -> record evidence and commit identity

如果 playbook 只能在部分节点成功,停止数据库对象更新,先修节点一致性。

14.5.3 从监控和日志识别加载失败

一张三层检查表

供应层

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

# 目标文件存在、owner/mode 正确、hash 与基线一致

数据库看到的 control:

SELECT
    name,
    default_version,
    installed_version,
    comment
FROM pg_available_extensions
WHERE name IN ('pg_trgm', 'vector');

若查不到,先看 server 实际 sharedir,不要先查 search_path

进程层

SELECT
    name,
    setting,
    source,
    sourcefile,
    pending_restart
FROM pg_settings
WHERE name = 'shared_preload_libraries';

再看每个实例启动日志:

could not access file ...
could not load library ...
undefined symbol ...
must be loaded via shared_preload_libraries ...

配置声明、Patroni dynamic config、磁盘配置与 live setting 可能暂时不同。 以 live + restart history + log 为准。

数据库层

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

再查:

SELECT * FROM pg_extension_update_paths('pg_trgm');

最后跑真正业务 probe。\dx 只证明 catalog 里有一行,不证明索引有效或 查询正确。

失败模式到动作

观察 解释 安全动作
pg_available_extensions 无记录 当前 server 看不到 control 核对节点/PG major/安装目录
available 有、installed 为空 包在,当前数据库未创建 走审批后的 CREATE EXTENSION
default > installed 支持文件较新、对象仍旧 评审 update path,不自动升级
installed 有、library 缺 节点漂移,failover 风险 阻断晋升,恢复 exact package
pending_restart=true 配置尚未生效 按 HA 策略滚动重启
primary 成功、replica 加载失败 主备供应不一致 停止变更/晋升,修所有副本
must be owner 生命周期权限边界生效 用受控 owner/admin migration
must be superuser untrusted/control 要求 管理员评审,禁止给 app 提权
object already exists schema/历史手工对象冲突 盘点依赖,禁止 CASCADE 硬装
no update path package 脚本图不支持 选择受支持中间版本或迁移方案

监控哪些事实

低基数状态:

extension_expected{cluster,db,name,version}
extension_installed{cluster,db,name,version}
extension_package_parity{cluster,name,build}
extension_preload_live{cluster,instance,name}
extension_probe_success{cluster,db,name}

不要把每个 SQL 对象或 hash 都做成高基数时序标签。详细成员、文件 hash 和 包清单保存在 inventory/evidence;监控只暴露是否与期望一致,并链接 runbook。

事件/日志告警:

  • postmaster 因库加载失败重启;
  • undefined symbol/ABI 错误;
  • extension update DDL 失败;
  • recovery/replica 上 extension function 报错;
  • extension 相关查询错误率突增;
  • ANN/特殊索引 invalid;
  • 更新后 P95/P99、WAL、内存、replica lag 越界。

L1 验证包

本章本地 evidence 明确写 pigsty_l1=not-run。在真实 L1 补齐:

00-inventory-commit.txt
01-repository-snapshot.txt
02-node-package-matrix.csv
03-control-library-hashes.csv
04-pg-settings-all-instances.csv
05-pg-available-versions.csv
06-pg-extension-all-databases.csv
07-member-and-index-catalog.csv
08-query-plan-and-correctness.txt
09-replica/failover-probe.txt
10-clean-restore-report.txt
11-reset-or-rollback-report.txt

每份 evidence 带:

captured_at
cluster/database/host
server and package build
command/tool version
change/commit identity
operator

凭证不得进入 evidence。

Pigsty 管理扩展的停止线

  • inventory 别名无法解析到目标 OS/PG major;
  • 本地仓库没有灾备/新节点需要的包;
  • 任一 replica 包/hash 不一致;
  • preload 变更未完成滚动重启;
  • 只在 postgres 数据库验证,业务数据库未盘点;
  • 默认 pg_trgm 与自定义 schema 声明冲突;
  • playbook 成功但原生 catalog/query probe 失败;
  • 没有 clean restore 与 major-upgrade 路线。

平台自动化可以缩短执行时间,不能降低验收标准。

本节结论

Pigsty 提供的是可声明、可重复的控制面:

package alias + cluster intent + preload + database declaration

PostgreSQL 提供的是 live 数据面事实:

files + settings + pg_extension + members + query behavior

两者一致,扩展才“可用”;再加升级、恢复和退出证据,才“可运营”。


上一节:生命周期与升级耦合 · 返回本章目录 · 下一节:建立可复用扩展 ADR · 查看全书目录 · 查看索引中心

14.6 建立可复用扩展 ADR

ADR(Architecture Decision Record)不是会议纪要,也不是给既定选择补理由。 它要让未来的维护者回答:

当时解决什么问题?
在什么版本和假设下?
比较了哪些替代?
什么证据使结论成立?
哪些风险仍然存在?
何时必须复审或退出?

本章提供 扩展 ADR 模板。模板不是为了 填满十个标题,而是强迫“价值—运行—退出”形成闭环。

14.6.1 问题、候选、假设与成功标准

标题写问题,不先写扩展

较差:

ADR-023: Adopt pgvector

更好:

ADR-023: Semantic nearest-neighbor retrieval for product support corpus

第二种标题允许结论是:

  • 采用 pgvector;
  • 采用另一个 PostgreSQL 扩展;
  • 使用外部服务;
  • 使用精确检索;
  • 现在不做。

候选没有绑架问题。

决策元数据

最小字段:

id: ADR-023
status: proposed
owners:
  product: ...
  application: ...
  database: ...
  platform: ...
created_at: ...
review_at: ...
decision_scope:
  environment: ...
  postgresql: ...
  pigsty: ...
  os_arch: ...

状态只用明确集合:

proposed -> pilot -> accepted
                    -> rejected
accepted/rejected -> superseded by ADR-N

不要把 pilot 当没有期限的半批准。它必须有 traffic/data/environment 边界、 停止标准和截止复审日。

问题陈述

写:

current behavior
observed evidence
business/technical impact
target SLO/quality
in scope
out of scope
do-nothing consequence

示例:

当前标题检索的零结果率为 X;
经标注样本确认 Y% 来自一个字符拼写误差;
目标只覆盖英文产品标题,返回上限 20,P95 < 50 ms;
中文分词、语义相关性和全站文档不在范围;
不改变时影响为 Z。

每个数字链接到 query snapshot、dashboard 或数据集版本。没有证据的假设 单独列:

assumptions:
  - typo distribution remains stable
  - title updates are below ...
  - one cluster can hold index within ...

后续证据推翻假设时自动触发复审。

候选集合

至少包含:

  1. 不做;
  2. PostgreSQL 原生机制;
  3. 候选扩展;
  4. 外部服务/应用实现(若实际可行)。

对每个候选用同一维度:

维度 不做 原生 扩展 A 外部服务
正确性/质量
P95/P99
写入与资源成本
一致性
HA/恢复
升级/供应
权限/安全
退出成本
团队技能/owner

不要把“扩展一行 SQL”与“外部服务完整运维”比较;每格都是完整方案。

成功标准与停止标准成对出现

示例:

success:
  relevance_at_20: ">= 0.82"
  p95_ms: "<= 50"
  p99_ms: "<= 100"
  replica_lag_p95_s: "<= 2"
  clean_restore: pass
  upgrade_rehearsal: pass

stop:
  crash_or_corruption: immediate
  wrong_result: immediate
  p99_ms: "> 200"
  wal_multiplier: "> 3"
  restore_rto: "> agreed budget"
  no_portable_export: reject

“比现在快”不是标准;“无明显问题”不是停止线。

分离硬门槛与权重

某些条件不可用加权分抵消:

hard gates:
  license approved
  target packages available on all nodes
  no correctness regression
  clean restore passes
  exit artifact exists
  security boundary accepted

weighted trade-offs:
  latency
  cost
  operator effort
  feature richness

否则一个非常快但不能恢复的扩展,可能用性能分“赢”过恢复硬门槛。

决策声明

结论写成:

We accept X
for problem Y
in environment/version boundary Z
because evidence A/B/C passed.

We do not approve M/N.
Residual risks are R.
Before production, gates G must pass.
Review is triggered by T.

这比“综合考虑后决定采用”更容易审计。

14.6.2 最小 PoC、风险清单与退出路径

PoC 的最小不是样本最少

最小 PoC 是覆盖决策最关键不确定性的最小实验。它不需要模拟所有生产流量, 但不能只跑 happy path。

扩展通用 PoC:

identity
  server/package/control/library/object versions

install
  intended role success
  unauthorized role failure
  preload/restart if required

behavior
  correctness and representative query
  indexes/plans
  boundary and adverse data

lifecycle
  update path
  update before/after regression
  physical standby/failover
  logical/dump behavior
  clean restore
  major upgrade clone

exit
  portable export
  dependency inventory
  removal without CASCADE

本章本地 PoC 覆盖其中 install、behavior、object update、dump 与文本出口; 没有覆盖 Pigsty L1、备库、clean restore 和 major upgrade,所以 vector 只能是 pilot。

fixture 必须确定性

记录:

  • schema/data version;
  • 生成方式;
  • 随机 seed;
  • 数据规模与分布;
  • query 参数;
  • expected rows/order/error;
  • baseline checksum。

本章不是比较浮点的无限精度,而固定六位小数与 top ID:

trigram scores:
  0.620690,0.305556,0.205128

vector L2:
  0.000000,0.141421,0.282843

对于近似索引,大数据 PoC 应定义 recall tolerance,而不是错误要求每次物理 计划与结果顺序完全相同。

正向、负向、破坏性测试分层

read-only:
  catalog, availability, plan, dependency

reversible DDL in isolated lab:
  CREATE/ALTER/DROP extension

fault injection:
  missing library, wrong preload, failover, crash

destructive lifecycle:
  restore, major upgrade, exit conversion

后两类必须在隔离 clone/L1 进行,有明确 target 与恢复路径。不要为了完成 ADR 在生产主库拔动态库。

本书 lab 的 destructive action 只接管:

pg36_shop/shop_ch14/pg_trgm+vector

并要求 marker、token、target 与无活跃 worker。生产迁移另写,不复用 “删掉重建”脚本。

evidence 不是终端滚屏

每轮输出一个不可变目录:

manifest.txt
package-manifest.txt
available-versions.csv
extension-inventory-before/after.csv
member-catalog-before/after.csv
security-catalog.csv
behavior-before/after.csv
plans
failure stdout/stderr/exit
database-schema.sql
selected-schema.sql
portable-export.csv
verify.txt
review.txt

manifest 包含:

captured_at
target/service
server/tool versions
validation path
source file hashes
proposal checksum

不要写密码、连接 URI secret 或生产个人数据。

风险清单有 owner 与触发器

风险 概率/影响 缓解 观测 owner trigger
package 在新 PG major 缺失 提前构建/替代 release matrix major roadmap
C library crash canary/rollback crash/restart error budget
ANN recall 漂移 golden corpus quality job model/data change
restore 缺旧脚本 repo snapshot restore drill retention review
vendor/license 改变 legal/exit periodic review new terms
node package drift Pigsty convergence parity probe failover/new node

没有 owner 的风险不是被管理,只是被记录。

退出路径从依赖图开始

SELECT
    d.classid::regclass,
    d.objid,
    d.deptype
FROM pg_depend AS d
JOIN pg_extension AS e
  ON e.oid = d.refobjid
WHERE d.refclassid = 'pg_extension'::regclass
  AND e.extname = 'vector';

还要查引用扩展成员的业务对象。退出步骤必须显式:

export/copy
  -> verify
  -> dual representation
  -> switch reads
  -> stop old writes
  -> remove business dependencies
  -> DROP EXTENSION RESTRICT
  -> remove preload/restart
  -> remove packages from nodes/repository only when safe

最后一步不是第一步。包删除前要考虑历史备份与降级节点。

验证退出,而不是只验证导出

退出 PoC 成功条件:

  • 导出行数/主键/checksum 匹配;
  • 目标表示能承载单位、坐标系、模型与精度;
  • 新查询结果和 SLO 在容差内;
  • 旧应用与新 schema 的兼容窗口成立;
  • 无残余 view/function/index/table 依赖;
  • DROP EXTENSION 在不使用 CASCADE 时成功;
  • 包与 preload 清理后实例重启、备库和恢复通过。

14.6.3 结论的版本范围和复审触发器

ADR 是带范围的结论

错误:

pgvector is approved.

可执行:

vector 0.8.4 is approved for a bounded pilot
on upstream PostgreSQL 18.6 / Ubuntu 24.04 amd64 / Pigsty 4.5,
using dimension D and model M,
for corpus C and query shape Q,
under package build B and SLO envelope E.

范围外不是自动拒绝,但必须重新验证。

版本块

scope:
  postgresql:
    implementation: upstream
    versions: ["18.6"]
  pigsty: ["4.4"]
  os_arch: ["ubuntu-24.04-amd64"]
  extension:
    sql_name: vector
    object_version: "0.8.4"
    package_build: "..."
  topology:
    primary: 1
    physical_standby: 2
  workload:
    corpus_version: "..."
    model: "..."
    dimension: 1536
    distance: cosine

“支持 PG14–18”可以是项目宣称;ADR 的验证范围可能只完成 17/18。两者分列。

复审触发器

日历触发:

每 6/12 个月
扩展或 PostgreSQL EOL 前
license/support 合同续签前

变更触发:

  • PostgreSQL major/minor 或内核供应者变化;
  • Pigsty release、OS、CPU architecture 变化;
  • extension project/package/object version 变化;
  • control 的 trusted/preload/requires/relocatable 变化;
  • 数据规模、分布、语言、模型、维度、距离度量变化;
  • 新建/替换 standby、灾备或恢复镜像;
  • SLO、错误预算或容量越界;
  • crash、错误结果、安全通告;
  • 维护者、许可证、供应商或仓库变化;
  • clean restore/upgrade drill 失败;
  • 退出成本估算越过窗口。

不覆盖历史,使用 supersede

决策改变时:

ADR-014 accepted pg_trgm 1.6 in scope X
ADR-028 supersedes ADR-014 for scope Y

保留旧 ADR:

  • 能解释旧备份/旧服务为何依赖它;
  • 能追踪当时证据;
  • 能区分错误决策与条件变化;
  • 能为事故和退出提供历史。

只在原文底部改“现在改用 Z”,会抹掉因果链。

把复审接入变更门禁

自动检查:

inventory package version changed
pg_extension extversion changed
control/library hash changed
server major changed
model/corpus identity changed

若任一发生:

baseline no longer matches
  -> block silent promotion
  -> open review
  -> run scoped test matrix
  -> issue new proposal checksum

不要让监控自动决定架构,但让它阻止“版本已经漂了,ADR 仍显示已批准”。

供第 15–17 章复用

后续三章沿用同一模板,但各自增加领域项:

第 15 章检索

language/tokenizer/dictionary
ranking and relevance corpus
query grammar and denial-of-service boundary
index pending-list/bloat/update cost

第 16 章时空

SRID/coordinate order/unit
geometry validity
spatial selectivity
time zone and temporal range
GIS export format

第 17 章分析与分布式

shard key/co-location
cross-shard transaction
rebalance/failure
columnar/OLAP consistency
capacity crossover point

它们可以增加字段,不能删掉供应、恢复、权限和退出。

ADR 验收问题

评审者逐句问:

  • 问题是否在没有候选扩展名时仍成立?
  • 是否有“不做”和原生替代?
  • 成功/停止标准是否能机器或人工复验?
  • 是否写了 exact server/package/object 版本?
  • 是否测过未授权失败?
  • 是否覆盖备库、clean restore 和 major upgrade?
  • 自定义数据能否导出,退出是否不用 CASCADE
  • 残余风险是否有 owner?
  • 哪个变化会让结论失效?
  • evidence 能否由另一位工程师重跑?

任一回答“以后再补”,ADR 状态最多是 proposed/pilot。

本节结论

好的扩展 ADR 不是“为什么喜欢它”,而是一个可撤销承诺:

under these facts,
for this problem,
this option passes these gates,
with these residual risks,
until one of these triggers changes.

它让采用扩展成为受控工程选择,而不是永久信仰。


上一节:用 Pigsty 管理扩展可用性 · 返回本章目录 · 下一节:实战:评审三个候选扩展 · 查看全书目录 · 查看索引中心

14.7 实战:评审三个候选扩展

本节把前六节压成一个 release proposal:

three problems
  -> three decisions
  -> package/control evidence
  -> privilege boundaries
  -> member/index/query evidence
  -> one object upgrade
  -> dump and portable exit
  -> exact reset and rebuild

它不是扩展性能评测,也不是生产安装脚本。实验的价值是证明评审结构能够 运行、失败、复位和重复。

环境与破坏边界

正式 evidence 来自 Homebrew PostgreSQL 18.6 直连服务,未在 Pigsty L1 运行。task.sh all 会精确删除并重建带本章 marker 的 shop_ch14pg_trgmvector,只适合本地/开发数据库。生产变更不得运行这一 “删后重建”入口。

14.7.1 一个接受、一个试点、一个拒绝

问题 A:有界单字段拼写容错

候选 pg_trgm

问题边界:

field: one title text column
query: typo-tolerant lookup
fixture typo: "PostgreSQL extenson"
result limit: 3
not in scope: language segmentation, semantic ranking, document search

原生替代:

  • 精确 B-tree;
  • 规范化前缀搜索;
  • PostgreSQL FTS;
  • 应用侧拼写纠正。

采用理由:

  • PostgreSQL contrib 扩展;
  • 当前 control 为 trusted/relocatable;
  • 不改变 title text 类型;
  • GIN gin_trgm_ops 可由目录与计划验证;
  • 可以先切回精确/FTS,再删 GIN 和扩展;
  • 1.3 → 1.6 更新路径与行为回归可重复。

结论:

accept pg_trgm
only for bounded fuzzy matching

不是批准它替代第 15 章的全部检索设计。

问题 B:语义近邻检索

候选 vector(项目/包别名常为 pgvector)。

本地 PoC:

type: vector(3)
distance: L2
index: HNSW vector_l2_ops
query vector: [1,0,0]
top ids: 1,2,5

已证明:

  • control/安装 SQL/动态库存在并有 hash;
  • trusted=false,非超级用户创建以 42501 失败;
  • 管理员能在目标 schema 创建 0.8.4;
  • 表、类型、HNSW opclass/index 有目录证据;
  • 应用角色可查询但没有表写权限;
  • embedding::text 可导出五行;
  • 全库 dump 用 CREATE EXTENSION vector 表示成员。

未证明:

  • 真实 embedding model、dimension 与 normalization;
  • 真实语料 relevance/recall;
  • 过滤组合下 ANN 行为;
  • 索引 build、内存、磁盘、WAL 与更新成本;
  • 并发 P95/P99;
  • 物理备库/failover;
  • clean restore;
  • PostgreSQL major upgrade;
  • 在 Pigsty L1 所有节点的包一致性。

结论:

pilot vector 0.8.4
bounded to an isolated workload and evidence plan

任何真实业务接入前必须补上第 15 章的质量语料与 L1 生命周期证据。

问题 C:分布式分片

候选 Citus。

当前事实:

no measured single-cluster capacity breach
no shard-key contract
no co-location model
no cross-shard transaction budget
no rebalance/failure test

本地 Homebrew server 的 pg_available_extensions 也没有 Citus,但这不是 拒绝的主要理由。Pigsty 当前扩展目录提供 Citus 14.0.0;平台有包仍不能替 架构证明问题。

当前替代:

  • 修正查询与索引;
  • 生命周期/归档治理;
  • PostgreSQL declarative partitioning;
  • 垂直扩容;
  • 读副本或分析副本;
  • 到第 17 章测量单集群容量边界。

重新打开 ADR 的条件:

measured capacity/SLO crossover
  + stable distribution key
  + transaction and uniqueness model
  + rebalance/failure/backup plan

结论:

reject Citus now

拒绝的是当前采用时机,不是产品评价。

把结论写进数据库

setup.sql 建立:

CREATE TABLE shop_ch14.extension_review (
    candidate text PRIMARY KEY,
    extension_name text NOT NULL,
    package_alias text NOT NULL,
    decision text NOT NULL
        CHECK (decision IN ('accept', 'pilot', 'reject')),
    problem text NOT NULL,
    success_criterion text NOT NULL,
    exit_path text NOT NULL,
    review_trigger text NOT NULL,
    reviewed_on date NOT NULL
);

最终必须精确得到:

citus:reject,pg_trgm:accept,vector:pilot

把 ADR 行放进实验数据库不是建议生产数据库存文档;它使 fixture checksum 同时覆盖数据与决策,防止测试脚本与文字结论分叉。

最小架构

shop_ch14
├── extension_review
├── candidate_doc
│   ├── title text
│   └── embedding vector(3)
├── pg_trgm 1.3 -> 1.6
│   └── GIN gin_trgm_ops
└── vector 0.8.4
    └── HNSW vector_l2_ops

所有 schema、扩展与非成员 relation/index 带 marker:

pg36 ch14 extension lifecycle lab; safe to rebuild

扩展成员通过 pg_depend.deptype='e' 识别,不要求逐个添加 comment。

14.7.2 在 L1 安装并验证原生对象与平台状态

标题中的 L1 是目标运行形态,不是本地证据伪装。流程分两步:

  1. 在受控直连 PostgreSQL 完成机制 fixture;
  2. 把同一合同移植到 Pigsty L1,补齐节点、HA 与恢复证据。

1. 准备 libpq service

[pg36-admin]
host=/path/to/socket-or-host
port=5432
dbname=pg36_shop
user=postgres
export PGSERVICEFILE=/path/to/pg_service.conf
export PGSERVICE=pg36-admin

service 文件权限收窄,不在命令行或 evidence 打印密码。

context guard 要求:

database=pg36_shop
writable primary/direct PostgreSQL
server major=14..18
session superuser=true
can SET ROLE pg36_owner
ch04-v1 model exists
pg36_app is constrained LOGIN
pg_trgm 1.3 and 1.6 support files available
vector 0.8.4 support files available

版本不符时脚本拒绝;读者应复制 proposal、更新版本与 golden 后重新评审, 不应删掉 guard。

2. 分阶段入口

./static/labs/ch14/task.sh setup
./static/labs/ch14/task.sh inventory
./static/labs/ch14/task.sh upgrade
./static/labs/ch14/task.sh dump

每个会精确重建 fixture,适合单独教学。最终只认:

PG36_EVIDENCE_DIR="$PWD/evidence/ch14" \
  ./static/labs/ch14/task.sh all

3. 先验证支持文件

package-manifest.txt 记录:

pg_config path/version
server major
sharedir/pkglibdir
validation_path=direct-postgresql
pigsty_l1=not-run

并对:

pg_trgm.control
pg_trgm--1.3.sql
pg_trgm--1.3--1.4.sql
pg_trgm--1.4--1.5.sql
pg_trgm--1.5--1.6.sql
pg_trgm.dylib/.so
vector.control
vector--0.8.4.sql
vector.dylib/.so

生成 SHA-256。

脚本先比较 pg_config major 与 live server major。PATH 指向错误 PG 安装时 立即失败,不会拿另一套支持文件做出“可用”结论。

在 Pigsty L1,这份清单要按所有主备 host 展开,而不是只在 primary 生成。

4. 碰撞保护与 trusted 安装

setup.sql 若发现:

  • shop_ch14 marker/owner 不符;
  • pg_trgmvector 已位于别的 schema;
  • extension marker、owner 或版本不在允许集合;
  • schema 中有未知非 extension relation/routine/type/operator/opclass;

就拒绝重建。

随后:

SET ROLE pg36_owner;

CREATE SCHEMA shop_ch14 AUTHORIZATION pg36_owner;

CREATE EXTENSION pg_trgm
  WITH SCHEMA shop_ch14
  VERSION '1.3';

RESET ROLE;

结果:

pg_trgm_owner=pg36_owner
pg_trgm_version=1.3
vector_installed=false

这证明 trusted 规则与数据库 owner 权限,不表示 pg36_owner 是超级用户。

5. 注入预期特权失败

owner-create-vector.sql

SET ROLE pg36_owner;
CREATE EXTENSION vector
  WITH SCHEMA shop_ch14
  VERSION '0.8.4';

必须:

psql exit=3
SQLSTATE=42501
Must be superuser to create this extension

若它意外成功,说明 control/权限环境与 proposal 不同,review 失败,而不是 把差异忽略。

管理员再执行 install-vector.sql

CREATE EXTENSION vector
  WITH SCHEMA shop_ch14
  VERSION '0.8.4';

并由 owner 建表:

CREATE TABLE shop_ch14.candidate_doc (
    doc_id bigint PRIMARY KEY,
    title text NOT NULL,
    embedding shop_ch14.vector(3) NOT NULL
);

6. 建立两个可验证索引

CREATE INDEX candidate_doc_title_trgm_idx
ON shop_ch14.candidate_doc
USING gin (title shop_ch14.gin_trgm_ops);

CREATE INDEX candidate_doc_embedding_hnsw_idx
ON shop_ch14.candidate_doc
USING hnsw (embedding shop_ch14.vector_l2_ops)
WITH (m = 8, ef_construction = 32);

目录验收:

index AM opclass valid/ready/live
candidate_doc_title_trgm_idx gin shop_ch14.gin_trgm_ops true/true/true
candidate_doc_embedding_hnsw_idx hnsw shop_ch14.vector_l2_ops true/true/true

CREATE INDEX 成功还不够;检查 pg_indexpg_ampg_opclass,防止 名字相同但实现漂移。

7. 采集扩展与成员目录

extension-inventory.sql

name
object version
owner
nominal schema
relocatable
superuser/trusted/requires
member count
marker

更新前 PostgreSQL 18.6:

pg_trgm  1.3    owner=pg36_owner  trusted=t  members=37
vector   0.8.4  owner=postgres    trusted=f  members=237

member-catalog.sql 再按 catalog 分解:

pg_trgm:
  pg_opclass, pg_operator, pg_opfamily, pg_proc, pg_type

vector:
  pg_am, pg_cast, pg_opclass, pg_operator,
  pg_opfamily, pg_proc, pg_type

更新后 pg_trgm 成员为 47。数量只冻结本次 PG18.6 build;其他 major 可有 条件差异。

8. 权限矩阵

pg36_app

USAGE shop_ch14       = true
SELECT review/docs    = true
INSERT/UPDATE/DELETE  = false
extension owner       = false

app-query.sql 成功使用函数、操作符与类型;随后:

ALTER EXTENSION pg_trgm UPDATE TO '1.6';

必须:

SQLSTATE 42501
must be owner of extension pg_trgm

应用使用能力与扩展管理权被分离。

9. 行为 baseline

模糊检索:

SELECT
    doc_id,
    round(
      shop_ch14.similarity(
        title,
        'PostgreSQL extenson'
      )::numeric,
      6
    ) AS score
FROM shop_ch14.candidate_doc
ORDER BY score DESC, doc_id
LIMIT 3;

结果:

1  0.620690
5  0.305556
2  0.205128

向量检索:

SELECT
    doc_id,
    round(
      (
        embedding
        OPERATOR(shop_ch14.<->)
        '[1,0,0]'::shop_ch14.vector(3)
      )::numeric,
      6
    ) AS distance
FROM shop_ch14.candidate_doc
ORDER BY
    embedding
      OPERATOR(shop_ch14.<->)
      '[1,0,0]'::shop_ch14.vector(3),
    doc_id
LIMIT 3;

结果:

1  0.000000
2  0.141421
5  0.282843

10. 索引计划

五行表优化器自然可能选择 seq scan。实验:

SET enable_seqscan = off;

只用于证明索引路径存在,不用于性能结论。

trigram:

Bitmap Index Scan on candidate_doc_title_trgm_idx

vector:

Index Scan using candidate_doc_embedding_hnsw_idx

生产验收应恢复默认 planner 配置,用真实数据比较:

EXPLAIN (ANALYZE, BUFFERS, WAL, SETTINGS)

并检查结果质量;不能用 enable_seqscan=off 证明索引值得使用。

11. 更新 1.3 → 1.6

update-paths.sql 先证明:

1.3--1.4--1.5--1.6

upgrade.sql 要求 source 精确为 1.3:

SET ROLE pg36_owner;
ALTER EXTENSION pg_trgm UPDATE TO '1.6';
RESET ROLE;

若 source 已经变化,返回本章自定义 P3640,不猜迁移路径。

更新后重新采集:

  • available version installed flag;
  • extension/member catalog;
  • index validity/opclass;
  • ACL;
  • 两个查询;
  • 两个计划。

pg_trgm_version 与成员清单外,行为 golden 不变。

12. dump 的正反例

全库:

pg_dump \
  --schema-only \
  --no-owner \
  --no-privileges \
  --dbname='service=pg36-admin' \
  > database-schema.sql

必须包含:

CREATE EXTENSION ... pg_trgm
CREATE EXTENSION ... vector

且不展开 shop_ch14 扩展成员函数/类型。

选择性 schema:

pg_dump \
  --schema-only \
  --schema=shop_ch14 \
  --no-owner \
  --no-privileges \
  --dbname='service=pg36-admin' \
  > selected-schema.sql

它包含应用表和索引,却没有 CREATE EXTENSION。review 把这个“不完整依赖” 作为预期证据,提醒恢复 runbook 先供应并创建扩展。

13. portable exit

portable-export.sql

doc_id,title,embedding_text
1,PostgreSQL extension guide,"[1,0,0]"
...

review 要求:

  • header 精确;
  • 五个主键按 1..5;
  • 每个 embedding 是 bracketed text。

生产退出还需导入目标、语义比对和删依赖;本章只证明可携带 representation。

14. 最终不变量

final-state.sql

review_rows=3
document_rows=5
pg_trgm_version=1.6
vector_version=0.8.4
pg_trgm_members=47
vector_members=237
trigram_top_ids=1,5,2
vector_top_ids=1,2,5
business_checksum=5398634500fe53ba1fb683e9a2c6e745

checksum 包含:

  • 三行 ADR 内容;
  • 五行文档/向量文本;
  • extension name/version/schema/relocatable。

它不包含管理员用户名,避免换一个受控超级用户就改变业务 golden。

15. 精确复位

手工入口:

PG36_RESET_TOKEN=RESET_CH14_EXTENSION_LAB \
PG36_RESET_TARGET='pg36_shop/shop_ch14/pg_trgm+vector' \
  ./static/labs/ch14/task.sh reset

reset.sql 在删除前验证:

  • database、writable instance、server 与角色;
  • token/target;
  • schema marker/owner;
  • extension name/version/schema/owner/marker;
  • 非成员 relation/type/routine/operator/opclass 白名单;
  • 没有 pg36-ch14-* 活跃 worker。

删除顺序:

DROP TABLE shop_ch14.candidate_doc;
DROP TABLE shop_ch14.extension_review;
DROP EXTENSION vector;
DROP EXTENSION pg_trgm;
DROP SCHEMA shop_ch14;

没有 CASCADE。若仍有未知业务依赖,DROP EXTENSION 失败并暴露它。

all 还注入:

wrong token  -> P3650
wrong target -> P3651
active worker -> P3653

拒绝后才精确复位,再完整重建第二遍。最终环境保留通过验收的 fixture。

16. 移植到 Pigsty L1

先审查 Pigsty 声明片段

pg_extensions:
  - pgvector

pg_databases:
  - name: pg36_shop
    schemas:
      - { name: app_ext, owner: pg36_owner }
    extensions:
      - { name: vector, schema: app_ext }

stock Pigsty 默认把 pg_trgm 启用在 public,无需与本地 shop_ch14 布局 完全相同。

L1 执行顺序:

review inventory diff
  -> verify repo/alias availability for exact PG/OS/arch
  -> install package on all nodes
  -> hash control/SQL/library on all nodes
  -> verify no preload requirement for these exact versions
  -> create extension through reviewed database migration
  -> query catalog/member/index/ACL
  -> run behavior and negative tests
  -> test replica query and controlled switchover
  -> clean restore to fresh L1/clone
  -> attach evidence to a new proposal

L1 不应强行复用本地 proposal checksum,因为:

  • schema 布局可能不同;
  • package build/OS 不同;
  • owner 名或 default extension state 不同;
  • 应补主备/restore evidence。

复制 ADR 结构,生成属于目标 L1 的新 baseline。

14.7.3 产出供 ch15–ch17 复用的 ADR 模板

交付包

本章交付不是一张“推荐扩展”表,而是:

candidate-review.md
extension-adr-template.md
baseline-v1.2-proposal.json
pigsty-declaration.example.yml
lab-contract.md
SQL/Bash/Python executable evidence chain

candidate-review.md 记录三项结论; extension-adr-template.md 提供十段结构:

  1. 决策元数据;
  2. 问题与边界;
  3. 候选与原生替代;
  4. 成功与停止标准;
  5. 供应链与运行条件;
  6. 数据与兼容性;
  7. 安全与治理;
  8. 最小 PoC;
  9. 退出路径;
  10. 结论。

第 15 章:检索候选如何复用

继承通用字段,再增加:

language/tokenizer/dictionary/config identity
query grammar
ranking formula
golden relevance corpus
GIN/GiST/RUM/other index behavior
write/pending-list/bloat cost
adversarial query boundary

pg_trgm 的 accept 不能自动批准所有字段。每个字段/查询形态仍需索引与 相关性 ADR。

vector 的 pilot 进入第 15 章后,要补:

embedding model/version
dimension
normalization
distance metric
exact-vs-ANN control
recall@k
filter selectivity
HNSW/IVFFlat build/update/maintenance

第 16 章:时空候选如何复用

增加:

SRID
coordinate order and units
geometry/geography choice
validity and precision
spatial predicate semantics
temporal interval/time zone
GiST/SP-GiST/BRIN behavior
WKT/WKB/GeoJSON export

PostGIS 若被采用,自定义类型的 restore/exit 门槛不能因为生态成熟而省略。

第 17 章:分析与分布式候选如何复用

增加:

single-node measured ceiling
shard/distribution key
co-location
global uniqueness/FK
cross-shard transaction
rebalance
node failure
DDL propagation
backup/restore and topology exit

Citus 只有在这些字段有证据后才从 reject 重新进入 proposed;“Pigsty 有包” 不是触发批准。

自动审校器检查什么

review.py 不比较终端输出的外观,而比较关系:

manifest proposal identity
package support-file hashes
two exact SQLSTATE 42501 failures
three candidate decisions and availability
before/after extversion
trusted/owner/schema/member relationships
update path
index AM/opclass/validity
least-privilege matrix
query results stable across update
forced index paths present
full dump vs selective dump semantics
portable export shape
final checksum
no-CASCADE reset source

关系式 review 比“命令 exit 0”更接近发布验收。

审校结果

正式两轮输出:

status=ok
decision=pg_trgm:accept/vector:pilot/citus:reject
boundary=package+control+database-object
failure=42501-owner+42501-superuser
upgrade=pg_trgm:1.3->1.6-behavior-stable
index=gin+hnsw
dump=create-extension+selective-dependency-warning
exit=portable-text-export
pigsty_l1=not-run
release=1.2-proposal
release_candidate_checksum=6a4b74baec5f522eb098c868f1d4f1b441bf5b5f6708411588af0a8793f7f573

第一轮通过后,脚本证明复位 guard,再删除并重建,第二轮得到同一关系和 proposal identity。

哪些结论可以带走

可以:

  • 扩展要同时管理供应、进程和数据库三层;
  • trusted/untrusted 与 owner 边界必须负面测试;
  • package version 与 extversion 分开;
  • update 前后比较 catalog、行为和计划;
  • dump 不携带支持文件,选择性 dump 不保证依赖闭包;
  • 自定义类型采用前先定义交换格式;
  • Pigsty 声明后回到原生证据;
  • ADR 允许 accept/pilot/reject,而不是所有候选二选一。

不能:

  • pg_trgm 对所有搜索都足够;
  • pgvector 0.8.4 已通过生产验证;
  • Citus 不值得使用;
  • PostgreSQL 14–17 会得到相同成员数;
  • Homebrew 文件 hash 能代表 Pigsty 包;
  • 五行查询速度能代表生产性能。

能清楚说出“实验没有证明什么”,是扩展治理成熟度的一部分。

本章最终检查

完成本章后,面对新扩展先写:

problem
native alternative
success/stop
data/exit
lifecycle
maintenance/license
privilege/supply
version scope
review triggers

然后才写:

CREATE EXTENSION ...

顺序反过来,数据库很快会积累一组谁也不敢升级、恢复或删除的隐性平台。


上一节:建立可复用扩展 ADR · 返回本章目录 · 下一章:见微知著:全文、模糊与向量检索 · 查看全书目录 · 查看索引中心