14.3 扩展选型的六个问题
扩展评审最容易从产品介绍开始:
它支持什么?更好的起点是:
我们已经观察到什么问题?本节用六个问题形成漏斗:
- 具体问题和原生替代是什么?
- 成功、停止与反例怎样测量?
- 是否引入数据格式锁定,怎样导出退出?
- 备份、复制与升级生命周期是否成立?
- 维护、许可证与商业连续性如何?
- 权限、崩溃面和供应链风险能否接受?
前两问证明价值,中间两问证明可运营,后两问证明风险归属。任何一问没有 答案,都只能进入调查或限域试点,不能直接成为平台默认。
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”也可能是成熟稳定。 用与你的风险相关的证据判断。
许可证检查三个层次
至少分别检查:
- 源码许可证;
- 二进制包与捆绑依赖;
- 企业使用、再分发、托管服务或商业功能条款。
不要从项目名称、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越早失败,越应尽早停止。没有实际问题时,不需要花几周证明供应链;问题 成立后,也不能用性能收益跳过恢复和退出。
上一节:内核、发行版与托管服务 · 返回本章目录 · 下一节:生命周期与升级耦合 · 查看全书目录 · 查看索引中心