15.6 扩展部署与运行代价
到目前为止,我们只证明了检索行为。生产采用还要回答:
每个节点有没有同一扩展构建?
数据库对象是谁创建和升级的?
索引建造、更新、WAL、备库与恢复要付什么代价?
文本怎样变成向量,谁有权把它发给谁?本节把 PostgreSQL 机制映射到 Pigsty 4.4,但仍以 live 节点、系统目录和实际 证据为准。
15.6.1 安装检索扩展并核对版本
复用第 14 章的三层状态
检索扩展仍然有三层:
package/support files on every host
-> backend can load compatible library
-> pg_extension object exists in this databasepg_trgm 随 PostgreSQL contrib 交付;vector 的项目/包常叫 pgvector,而
SQL 扩展名是 vector。不要混写:
package alias: pgvector
SQL: CREATE EXTENSION vector
catalog: pg_extension.extname = 'vector'本章不会安装或升级它们,而是要求第 14 章最终状态:
pg_trgm 1.6
vector 0.8.4
schema shop_ch14
exact extension markers如果版本、owner、schema 或 marker 不符,context.sql 拒绝。下游实验不能
悄悄接管上游扩展。
Pigsty 中分开“装包”和“启用”
Pigsty 4.4 当前文档指出:
pgvector随pgsql-main默认安装;pg_trgm位于默认启用扩展列表;- 可通过
pg_packages/pg_extensions影响包; - 可通过
pg_default_extensions影响数据库默认启用对象。
本章给出的
pigsty-declaration.example.yml
只是合并片段:
all:
vars:
pg_version: 18
pg_packages:
- pgsql-main
- pgvector
pg_default_extensions:
- { name: pg_trgm, schema: public }
pg_databases:
- name: pg36_shop
owner: pg36_owner
extensions:
- { name: vector, schema: public }实际版本中 pgvector 可能已被 pgsql-main 包别名覆盖,重复声明是否允许、
包名如何展开、目标 OS/PG major 是否有构建,都要用目标 inventory 与
Pigsty 文档确认。
教学夹具把扩展放在 shop_ch14,是为了凸显 namespace/owner。生产片段使用
public 只是示例,不是推荐所有扩展都堆进 public。schema 选择必须同时
满足:
- extension 是否 relocatable/是否强制 schema;
- 应用是否用全限定名称;
search_path安全;- dump/restore;
- 运维与升级脚本;
- 权限最小化。
L1 每个节点都要核对
物理复制会把数据库对象复制到备库,但不会把 OS 软件包和动态库通过 WAL
复制过去。主库 CREATE EXTENSION vector 成功,某备库仍可能因缺
vector.so 在查询、恢复或升主后失败。
对每个 L1 host 收集:
pg_config --version
pg_config --sharedir
pg_config --pkglibdir
test -r "$(pg_config --sharedir)/extension/vector.control"
test -r "$(pg_config --sharedir)/extension/pg_trgm.control"再核对目标 major 的 package manager 版本与文件 hash。不能从当前 shell
PATH 里的 pg_config 猜正在运行的 server;先从实例确认 server major,
再找对应安装树。
数据库内:
SELECT
e.extname,
e.extversion,
n.nspname AS schema_name,
pg_get_userbyid(e.extowner) AS owner,
e.extrelocatable
FROM pg_extension AS e
JOIN pg_namespace AS n
ON n.oid = e.extnamespace
WHERE e.extname IN ('pg_trgm', 'vector')
ORDER BY e.extname;控制文件可见性:
SELECT
name,
version,
installed,
superuser,
trusted,
relocatable
FROM pg_available_extension_versions
WHERE name IN ('pg_trgm', 'vector')
ORDER BY name, version;两份目录回答不同问题:
pg_available_extension_versions:server 支持文件允许哪些版本;pg_extension:当前 database 创建了哪个对象版本。
安装权限不要交给应用
本章角色边界:
platform/admin:
package / untrusted extension / lifecycle
pg36_owner (NOLOGIN or controlled SET ROLE):
schema, tables, reviewed migrations
pg36_app:
SELECT search interface
no table DML
no extension lifecyclepg36_app 对 product_search UPDATE 必须返回:
SQLSTATE 42501
permission denied for table product_search真实商品服务当然需要写入,但写角色不等于在线读角色必须拥有表级任意 UPDATE。 可使用:
- 受控迁移角色;
- 列级权限;
- 参数化函数;
- 独立异步 embedding worker;
- 队列/作业表状态机。
尤其不要让业务请求角色创建、升级或删除扩展。
证据边界
本章正式运行:
Homebrew PostgreSQL 18.4
direct PostgreSQL service
Pigsty 4.4 docs reviewed
Pigsty L1 execution not run所以 manifest 明确:
validation_path=direct-postgresql
pigsty_l1=not-run移植到 Pigsty 后要补:
inventory commit
repository/package resolution
every-node file/version/hash
database extension catalog
primary/replica query
failover/failback
backup clean restore15.6.2 观察索引体积、构建、查询和维护
先列出四类物理对象
本章商品表有:
heap + toast (if needed)
FTS GIN
title trigram GIN
embedding HNSW
active/category partial B-tree基础盘点:
SELECT
pg_relation_size('shop_ch15.product_search') AS heap_bytes,
pg_total_relation_size('shop_ch15.product_search') AS total_bytes,
pg_relation_size('shop_ch15.product_search_fts_idx') AS fts_bytes,
pg_relation_size('shop_ch15.product_search_title_trgm_idx') AS trgm_bytes,
pg_relation_size('shop_ch15.product_search_embedding_hnsw_idx') AS hnsw_bytes;17 行夹具看到的 8/16/32 KiB 级数字主要由最小页分配决定,不能计算
“每百万商品多少 GiB”。size-catalog.csv 只是证明对象存在且非零。
生产 size baseline 应记录:
row count and active count
average text/vector width
model dimension/type
index definition/options
data and query distribution
build/update age
server/filesystem/compression否则两个 size 数字不可比较。
构建要观察过程与阻塞
初始离线载入通常:
load data
-> analyze
-> create indexes比逐行维护索引高效。在线已有表则通常考虑:
CREATE INDEX CONCURRENTLY ...它减少对写入的阻塞,却有更长构建时间、额外扫描、资源占用和失败状态。运行时 观察:
SELECT
pid,
datname,
relid::regclass,
index_relid::regclass,
command,
phase,
lockers_total,
lockers_done,
blocks_total,
blocks_done,
tuples_total,
tuples_done
FROM pg_stat_progress_create_index;并结合:
SELECT *
FROM pg_locks
WHERE pid = :builder_pid;发布后必须确认 pg_index.indisvalid/indisready/indislive,不能只看 DDL
client exit。
查询观测分机械与用户两层
机械层:
EXPLAIN (ANALYZE, BUFFERS, WAL, SETTINGS)
SELECT ...;看:
- index/seq/bitmap path;
- actual rows 与估算;
- filter removed rows;
- buffers 与 temp I/O;
- planning/execution time;
- 生效的非默认 settings。
用户层:
query segment
candidate/result count
quality metric
P50/P95/P99 latency
timeout/error/no-result
model/index/release version执行快但 recall 差,不是成功;quality 高但 P99 超时,也不是成功。
Pigsty 默认启用/预加载的 pg_stat_statements 可帮助聚合 SQL 调用与耗时,
但要为检索 query 保持可归一的 SQL 形状,并将业务 query id/segment 放在
受控日志或 trace,而不是拼进 SQL 注释造成 statement 指纹爆炸。
写入成本要单独压测
文本变化会更新两个 GIN 和 generated tsvector;向量变化会更新 HNSW。
测:
rows/s
WAL bytes/row
CPU
index growth
autovacuum cadence
dead tuples
replica replay lag
checkpoint/write latency可以在受控事务前后比较:
SELECT pg_current_wal_lsn();
-- representative batch
SELECT pg_current_wal_lsn();再用 pg_wal_lsn_diff 估算该受控批次产生的 WAL。并发生产环境还有其他
事务,不能把全局 LSN 差未经隔离就归给一个作业。
HNSW 维护不是普通 B-tree 的复制粘贴
pgvector 官方说明 HNSW vacuum 可能耗时,并给出先 REINDEX INDEX CONCURRENTLY 再 VACUUM 的一种加速建议。这是需要谨慎评估的维护动作,
不是每晚固定模板:
- reindex 需要额外磁盘与构建资源;
- concurrently 有更长窗口和失败恢复;
- vacuum 仍要处理 heap;
- 副本会重放相关 WAL;
- 索引重建期间 recall/latency 要观测;
- 新 index 的参数与 opclass 必须一致。
先在代表性副本/演练环境测周期,再决定维护阈值。
监控矩阵
| 维度 | PostgreSQL 证据 | Pigsty/平台视图 |
|---|---|---|
| SQL 调用/耗时 | pg_stat_statements、logs | dashboard/alerts |
| 执行计划 | EXPLAIN、auto_explain | 集中日志 |
| index 使用 | pg_stat_user_indexes | 表/索引面板 |
| 表生命周期 | pg_stat_user_tables | vacuum/bloat 面板 |
| 构建进度 | pg_stat_progress_create_index | 变更任务证据 |
| WAL/副本 | LSN、replication views | HA/replication 面板 |
| 主机资源 | PostgreSQL/OS stats | node/PG exporter |
| 质量/ANN recall | 自建 eval job | release/SLO dashboard |
最后一行不能由通用数据库 exporter 自动推导。检索质量是业务测量,必须把 evaluation job 当成一等生产组件。
15.6.3 外部嵌入生成的权限、费用与数据边界
不要从数据库触发器同步调用外部 API
一个危险设计:
UPDATE product title
-> trigger
-> HTTP embedding API
-> wait inside transaction它把:
- 外部网络延迟;
- rate limit;
- provider outage;
- 费用;
- 密钥;
- 不确定重试;
放进数据库锁与事务寿命。远端已收费成功、本地事务却回滚时,还会出现不可 原子化的副作用。
更稳健的异步结构:
transaction:
update source text
record source_text_hash / desired_model / pending job
commit
worker:
claim job with bounded lease
build exact versioned input
call model service
validate dimension/norm
write vector + model_id + source_hash
mark success or retry state可用 outbox、作业表或消息系统实现;第 13 章已经讨论过触发器与异步边界。
幂等身份
一次 embedding 任务的自然 key 可以是:
(document_id, source_text_hash, model_id, template_version)写回时做 compare-and-set:
UPDATE product_embedding
SET embedding = :vector,
status = 'ready',
embedded_at = clock_timestamp()
WHERE product_id = :id
AND source_text_hash = :hash
AND model_id = :model
AND status = 'processing';如果源文本在推理期间变化,旧结果不能覆盖新版本。重试同一 key 应复用已完成 结果或安全 upsert,避免重复收费。
worker 最小权限
embedding worker 通常需要:
- 读取允许外发的字段;
- 读取/更新自己的 job;
- 写特定 vector/model/status 列;
- 不需要 DDL、扩展 owner、超级用户;
- 不需要任意读其他敏感 schema。
API secret 放在 secret manager/受控运行环境,不存进 SQL、YAML 仓库、表 comment 或 evidence manifest。Pigsty 负责 PostgreSQL 平台,不意味着模型 密钥应注入数据库 server 进程。
若文本不能离开边界,可以:
- 在受控网络自托管模型;
- 只对批准字段生成;
- 做脱敏/分区处理;
- 或拒绝向量方案。
“先接 API,之后再补合规”不是试点策略。
费用模型
至少估算:
[ Cost = InitialBackfillTokens \times Price
- DailyChangedTokens \times Price
- QueryTokens \times Price
- RetryWaste
- Storage/Index/Compute ]
还包括:
- backfill 期间 API 并发与限流;
- 模型升级全量重算;
- 双版本存储与索引;
- 失败重试和重复调用;
- query embedding cache;
- 数据出口与网络;
- 本地推理 GPU/CPU 和运维。
费用控制要有:
per-job token/byte limit
daily/project budget
rate/concurrency limit
retry ceiling and dead-letter state
backfill pause/resume
cost attribution by model/version缓存 query vector 时,key 必须包含 exact normalized text、model、template 与 版本;只按原始字符串缓存会在模型切换时串用旧空间。
数据生命周期要双向传播
删除或更正 source document 时,检查:
primary row
vector row/index
job queue and retry payload
query/result caches
logs/traces
offline evaluation exports
backups and retention
provider-side retained data物理删除在 PostgreSQL 中还受 MVCC、vacuum、WAL 与备份保留影响。法律上的
删除承诺必须和备份/恢复政策一致,不能只执行一条 DELETE。
故障与降级
模型服务不可用时,搜索不一定要整体不可用:
vector generation outage:
keep last valid vector with staleness marker
queue updates
query embedding outage:
fall back to FTS + trigram
expose degraded-mode metric
ANN/index incident:
exact path for small filtered sets
or lexical-only bounded fallback每种 fallback 都要在离线标注集上测质量、在线压测容量,且不能放宽权限过滤。
上线前问题
- 哪些字段能外发,依据是什么?
- provider 是否保留输入/输出、是否用于训练?
- 模型版本是否可固定,变更如何通知?
- 单次、每日、回填、升级的费用上限是什么?
- 谁能读原文、调用服务、写 vector?
- 如何证明 vector 对应当前 source hash?
- 失败如何重试,何时进入 dead letter?
- 模型切换如何双写、评估、回退?
- 删除、备份与 provider 侧数据如何协调?
- 无模型服务时,业务还能提供什么质量的结果?
回答不了这些问题时,向量 PoC 可以继续离线,不能进入生产写链路。
上一节:混合检索与排序验证 · 返回本章目录 · 下一节:实战:pg36_shop 商品混合检索 PoC ·
查看全书目录 · 查看索引中心