跳至内容
14.7 实战:评审三个候选扩展

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.4 直连服务,未在 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);

目录验收:

indexAMopclassvalid/ready/live
candidate_doc_title_trgm_idxginshop_ch14.gin_trgm_opstrue/true/true
candidate_doc_embedding_hnsw_idxhnswshop_ch14.vector_l2_opstrue/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.4:

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.4 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 · 返回本章目录 · 下一章:见微知著:全文、模糊与向量检索 · 查看全书目录 · 查看索引中心

最后更新于