# 建立可复用扩展 ADR

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

---

ADR（Architecture Decision Record）不是会议纪要，也不是给既定选择补理由。
它要让未来的维护者回答：

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

本章提供 [扩展 ADR 模板](/labs/ch14/extension-adr-template.md)。模板不是为了
填满十个标题，而是强迫“价值—运行—退出”形成闭环。

## 14.6.1 问题、候选、假设与成功标准 {#item-14-6-1}

### 标题写问题，不先写扩展

较差：

```text
ADR-023: Adopt pgvector
```

更好：

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

第二种标题允许结论是：

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

候选没有绑架问题。

### 决策元数据

最小字段：

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

状态只用明确集合：

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

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

### 问题陈述

写：

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

示例：

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

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

```yaml
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”与“外部服务完整运维”比较；每格都是完整方案。

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

示例：

```yaml
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
```

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

### 分离硬门槛与权重

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

```text
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
```

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

### 决策声明

结论写成：

```text
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、风险清单与退出路径 {#item-14-6-2}

### PoC 的最小不是样本最少

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

扩展通用 PoC：

```text
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：

```text
trigram scores:
  0.620690,0.305556,0.205128

vector L2:
  0.000000,0.141421,0.282843
```

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

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

```text
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 只接管：

```text
pg36_shop/shop_ch14/pg_trgm+vector
```

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

### evidence 不是终端滚屏

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

```text
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 包含：

```text
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 的风险不是被管理，只是被记录。

### 退出路径从依赖图开始

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

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

```text
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 结论的版本范围和复审触发器 {#item-14-6-3}

### ADR 是带范围的结论

错误：

```text
pgvector is approved.
```

可执行：

```text
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.
```

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

### 版本块

```yaml
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。两者分列。

### 复审触发器

日历触发：

```text
每 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

决策改变时：

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

保留旧 ADR：

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

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

### 把复审接入变更门禁

自动检查：

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

若任一发生：

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

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

### 供第 15–17 章复用

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

#### 第 15 章检索

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

#### 第 16 章时空

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

#### 第 17 章分析与分布式

```text
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 不是“为什么喜欢它”，而是一个可撤销承诺：

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

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

---

[上一节：用 Pigsty 管理扩展可用性](../05/) · [返回本章目录](../) · [下一节：实战：评审三个候选扩展](../07/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
