# 扩展选型的六个问题

LLMS 索引： [llms.txt](/llms.txt)

---

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

```text
它支持什么？
```

更好的起点是：

```text
我们已经观察到什么问题？
```

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

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

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

## 14.3.1 它解决的具体问题和原生替代是什么 {#item-14-3-1}

### 问题必须可证伪

以下不是问题陈述：

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

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

```text
workload + current evidence + target + boundary
```

例如：

```text
商品标题查询中，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；外部服务增加分布式复杂性，却可能提供独立扩缩容与专用算法。
这是工程权衡，不是“数据库内一定更快”。

### 比较单位是完整方案

不要比较：

```text
one SQL function vs one HTTP call
```

而比较：

```text
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 的成功标准故意很窄：

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

它证明 API 与索引机制，不证明：

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

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

### 反例必须进入数据集

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

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

分布式候选则要加入：

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

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

## 14.3.2 数据格式是否锁定、能否导出和退出 {#item-14-3-2}

### 第三问：锁定发生在哪里

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

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

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

`vector` 让：

```sql
embedding shop_ch14.vector(3)
```

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

```sql
DROP EXTENSION vector;
```

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

### 在采用前写出出口

本章先建立可移植导出：

```sql
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);
```

输出形如：

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

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

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

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

### 不把逻辑 dump 当数据出口

全库 `pg_dump` 通常写：

```sql
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。

### 第四问：生命周期是否成立

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

```text
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 维护活跃度、许可证与商业连续性 {#item-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、发布带宽和联合支持问题。两者都要问：

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

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

### 建立维护快照

ADR 中保存带日期的证据：

```yaml
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 权限、崩溃面与供应链风险 {#item-14-3-4}

### 第六问：谁获得什么能力

扩展评审画出特权链：

```text
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、超大查询和对象劫持风险。

### 供应链不止校验下载文件

最小证据链：

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

本章 [package manifest](/labs/ch14/task.sh) 对这些具体文件做 SHA-256：

```text
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 的
“创建成功”验收。

### 决策状态

只允许清晰状态：

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

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

### 本节结论

六问的顺序很重要：

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

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

---

[上一节：内核、发行版与托管服务](../02/) · [返回本章目录](../) · [下一节：生命周期与升级耦合](../04/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
