# 从数据库产品到能力组合

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

---

当团队说“我们用 PostgreSQL”时，这句话通常混合了三种不同事实：

```text
product
  一个特定版本的 PostgreSQL server

capabilities
  事务、约束、检索、时空、分析、复制、恢复……

service
  带身份、入口、目标、配额、支持和生命周期的对外交付
```

产品可以启动，不代表所有能力可用；能力可以运行，不代表服务可承诺。平台
设计的第一步，是把这三层重新拆开。

## 18.1.1 事务、检索、时空、分析与任务能力 {#item-18-1-1}

### 从业务问题开始，而不是从组件清单开始

`pg36_shop` 至少需要处理以下业务问题：

| 业务问题 | 需要的能力 | 首选正确性边界 |
|---|---|---|
| 订单与库存能否保持不变量 | 关系约束、事务、并发控制 | PostgreSQL commit |
| 应用如何安全调用数据能力 | 角色、schema、prepared SQL、API 合同 | DB + 应用 |
| 商品如何被关键词找到 | 全文/模糊检索、排序、质量 golden | PostgreSQL 起步 |
| 商品如何按语义相似找到 | embedding、向量距离、ANN 质量 | 试点 |
| 配送事件在哪里、何时有效 | 空间、时间、边界和参考系 | PostgreSQL 起步 |
| 月报如何快速且不过度影响交易 | 并行、索引、汇总、离线读 | PostgreSQL + 隔离 |
| 订单事件如何送到其他服务 | outbox、relay、消息投递 | 跨系统合同 |
| 图片字节放在哪里 | 对象存储与元数据协调 | 按数据域拆分 |

先写能力，能避免两种常见偷换：

```text
“PostgreSQL 支持” -> “我们的服务已经支持”
“引入了某产品”   -> “业务问题已经解决”
```

例如，`vector` 扩展已经在第 14 章的开发 fixture 中安装，`<=>` 距离查询也在
第 15 章得到正确结果。这证明“语义检索试验可运行”，没有证明：

- embedding 模型会被稳定版本化；
- ANN 在生产规模下满足质量与延迟；
- 索引重建落在维护窗口；
- 副本、备份、恢复和升级路径全部可用；
- 团队可以在模型漂移时诊断结果变化。

所以它的生命周期是 `pilot`，不是 `accepted`。

### 事务能力是权威状态的锚

对订单、库存和支付，最重要的不是“能存 JSON”或“QPS 很高”，而是所有
写入者看到同一套不可违反的事实：

```text
order total = sum(order item totals)
payment currency = order currency
inventory cannot be consumed below the approved boundary
status transition belongs to a finite allowed graph
duplicate request does not create a second business effect
```

第 4、10、13 章分别从约束、并发与数据库逻辑证明了这些能力。只要这类状态
仍由 PostgreSQL 定夺，缓存、搜索索引、事件流和湖仓都只能是派生物。

“source of truth” 容易变成口号，更精确的写法是按数据域声明 authority：

```text
product business state          -> PostgreSQL
cache entry bytes               -> cache
order publication intent        -> PostgreSQL outbox
message delivery/replay log     -> event bus
media object bytes              -> object storage
media identity/state/checksum   -> PostgreSQL
analytical projection           -> lakehouse
operational business state      -> PostgreSQL
```

冲突时相信谁，必须在发生冲突前写清楚。

### 检索是一组能力，不是一条 `LIKE`

商品检索至少可以分成：

```text
exact identity lookup
prefix / substring
typo-tolerant fuzzy match
lexical relevance
phrase / field weighting
semantic similarity
filter + rank
faceting / aggregation
highlighting
freshness and deletion
```

第 15 章的质量 golden 证明 `pg_trgm` 适合当前模糊检索基线，`vector` 适合
受控 pilot。它没有把“搜索”宣布为永久留在 PostgreSQL。蓝图保存一个明确
触发器：

> 当质量、规模、语言能力或独立可用性目标击败 PostgreSQL 基线时，才启用
> `external-search-projection-v1`。

这样，外部检索是一个由证据触发、可重建、可退出的投影，而不是架构图里一
开始就存在的时髦方框。

### 时空能力先统一语义，再比较引擎

空间系统最危险的错误往往不是查询慢，而是答案看起来合理：

```text
SRID 混用
经纬度顺序颠倒
边界包含规则不一致
event time 与 ingest time 混淆
本地时区被当 UTC
有效时间区间重叠
迟到事件被静默丢弃
```

第 16 章把 EPSG:4326、UTC、区间与边界规则固化为 fixture。PostGIS 让空间
类型、操作符与索引进入 SQL，但业务合同仍然高于扩展：迁移到其他引擎时，
这些语义必须不变。

因此 capability 的结构至少要包含：

```json
{
  "placement": "postgresql",
  "system_of_record": "postgresql",
  "state": "conditional",
  "consistency": "UTC + versioned validity + EPSG:4326",
  "freshness": "event ingest plus measured lateness",
  "externalization_trigger": "retention or throughput misses objective"
}
```

### 分析能力先减少工作，再横向扩展

第 17 章已经建立顺序：

```text
正确性 golden
  -> 计划与统计
  -> 索引 / BRIN
  -> 并行
  -> spill 证据
  -> 汇总
  -> OLTP/OLAP 隔离
  -> 分布式门槛
```

当前蓝图选择“本地汇总 + offline replica”作为第一步，而不是选择 Citus。
这不是永久拒绝分布式；第 26 章的容量证据可以触发新 ADR。

同理，`postgres_fdw` 的 loopback fixture 只证明过滤、聚合和部分失败的
查询形状。PostgreSQL 官方
[Foreign Data](https://www.postgresql.org/docs/18/ddl-foreign-data.html)
说明外表通过 wrapper 访问外部数据，并通过 user mapping 提供认证信息。
本书实验使用的 `password_required=false` 没有生产身份、网络与故障域，
所以严格标为 `lab-only`。

### 任务能力要分事务内与事务外

“数据库里能执行函数”不等于“任何业务任务都应放进事务”。一个实用边界：

留在事务内：

```text
约束检查
小而有界的派生值
幂等状态转换
短路径的 outbox 写入
与当前事务必须原子完成的审计事实
```

移到事务外：

```text
HTTP / RPC
发送邮件或短信
大批量重算
不确定时长的模型推理
跨系统补偿流程
无限或长期重试
```

事务内代码失败可以回滚；远端世界通常不能跟着 PostgreSQL 回滚。把两者硬塞
在一起，会产生持锁时间、连接占用、重试重复和不可控级联。

### 能力清单必须有状态

本章采用以下生命周期词汇：

| 状态 | 含义 |
|---|---|
| `accepted` | 已有当前范围证据，仍受版本与服务门约束 |
| `accepted-with-scope` | 只在明确狭窄边界内接受 |
| `accepted-first-step` | 当前首选，但容量触发器仍待观察 |
| `conditional` | 前置门尚未全部通过 |
| `pilot` | 只允许受控试验，不进入默认生产服务 |
| `lab-only` | 教学机制证据，禁止生产准入 |
| `deferred` | 触发条件未出现，不增加系统 |

没有状态的能力清单会把“听说过”“装得上”“试过一次”和“可值班”放在同一
列，最终无法治理。

## 18.1.2 计算、存储、接入、控制与观察平面 {#item-18-1-2}

能力回答“要做什么”，平面回答“由哪类机制完成”。

### 数据与计算平面

数据/计算平面直接处理请求与业务状态：

```text
PostgreSQL backend
tables / indexes / WAL-visible changes
SQL planner and executor
constraints / functions / triggers
extension types and operators
replica read execution
external projection workers
```

它的典型证据是：

- SQL 结果和业务校验和；
- 执行计划与实际行数；
- 事务、锁与 SQLSTATE；
- replica replay 位置；
- 投影 watermark。

在这个平面上，“成功”意味着某次业务操作按合同完成，不代表平台整体健康。

### 存储平面

存储平面不仅是 `PGDATA`：

| 存储 | 保存什么 | 关键风险 |
|---|---|---|
| PostgreSQL data files | heap、index、catalog | 损坏、容量、延迟 |
| WAL | 恢复与复制所需变化 | 归档缺口、保留爆炸 |
| backup repository | base/diff/incr 与 WAL | 不可恢复、凭据、同域失效 |
| temp | sort/hash 等中间结果 | 磁盘耗尽、I/O 争用 |
| object storage | 媒体或备份对象 | 清单、版本、删除 |
| cache | 可丢弃派生数据 | 陈旧、穿透、错误权威 |
| analytical/search index | 可重建投影 | lag、语义漂移、删除遗漏 |

“数据有三份”不是恢复策略。三份流复制副本会复制同一条误删；三个位于同一
存储故障域的节点也不是三个独立副本。第 21、32、35 章会分别验证备份恢复、
PITR 与损坏取证。

### 接入平面

接入平面把服务语义变成客户端可连接的端点：

```text
name / VIP / address
port
TLS and authentication
pooling mode
target selector
health check
failover routing
connection and queue budget
session-state contract
```

一个主库 IP 只是位置，不是服务。客户端需要知道：

```text
这个入口是否可写
是否允许陈旧读
是否 read-your-writes
发生切换时连接怎样断开
事务池能否使用 session feature
取消与超时如何传播
```

Pigsty 的
[Service Access](https://pigsty.io/docs/concept/ha/svc/)
用端口和 selector 表达 `primary`、`replica`、`default`、`offline` 等服务。
第 22 章会把这些入口与 PgBouncer 语义、连接预算一起验收。

### 控制平面

控制平面改变系统的期望状态：

```text
inventory and configuration
package and extension versions
cluster membership
leader election
switchover/failover decision
backup schedule and retention
role / database provisioning
change rollout and rollback
```

控制平面故障与数据平面故障不同。数据库可以继续处理流量，而 inventory 已经
漂移；也可能数据库本身健康，但错误的健康检查把流量切走。

控制平面必须回答：

- 谁能改；
- 改什么对象；
- 如何审阅；
- 是否幂等；
- 如何观察收敛；
- 哪一步是最后可逆点；
- 失败时由谁接管。

Ansible 成功退出只说明某次自动化执行没有报告失败；它不是业务 SLO 证据。

### 观察平面

观察平面收集并解释：

```text
metrics
logs
traces
catalog snapshots
query fingerprints and plans
backup manifests
configuration drift
synthetic probes
business invariants
```

观察平面不应只回答“图绿不绿”，还要让值班者完成因果链：

```text
用户症状
  -> 受影响的服务入口
  -> 当前拓扑与流量目标
  -> 数据库等待/资源/复制/恢复状态
  -> 最近变更
  -> 可逆且风险最小的动作
```

第 25 章会验证信号到 runbook 的连接。本章只列出必须覆盖的八类信号，不声称
告警已经有效。

### 管理平面与业务平面不要共用身份

一个常见反模式：

```text
application connection
  = object owner
  = extension installer
  = backup operator
  = cluster administrator
```

本书 fixture 已把角色分开：

| 身份 | 当前边界 |
|---|---|
| `pg36_app` | LOGIN、非 superuser、运行时 |
| `pg36_owner` | NOLOGIN、对象所有者 |
| `postgres` | 本地正式实验管理员 |

这还不是完整生产角色模型。第 23 章要继续拆出迁移、监控、备份、复制、审计
与应急身份。

### 一项能力会穿过多个平面

以“商品模糊搜索”为例：

```text
data/compute  pg_trgm operator + GIN/GiST plan
storage       product table, index, WAL, backup
access        read endpoint, role, timeout
control       extension package/version, CREATE EXTENSION, schema
observe       latency, quality golden, index build, bloat, errors
governance    owner, lifecycle, upgrade and exit
```

只验证 SQL 正确，会漏掉四个平面；只部署组件，则连第一项也未必正确。

## 18.1.3 组件组合必须有统一服务目标 {#item-18-1-3}

### 局部健康不推出整体健康

假设请求路径是：

```text
client
  -> DNS/VIP
  -> HAProxy
  -> PgBouncer
  -> PostgreSQL primary
  -> transaction
  -> outbox
  -> relay
  -> event bus
  -> consumer
```

每个组件都有自己的“up”，但业务关心的是：

```text
订单是否只创建一次
提交后多久可查询
事件多久送达
失败后是否可安全重试
是否会丢、重、乱序
能否在目标时间内恢复
```

如果数据库 20ms 提交、relay 卡 40 分钟，订单 API 的数据库延迟指标仍然很
漂亮，业务事件服务却已经违约。

### 先定义服务，再分配组件目标

一个服务目标模板：

| 维度 | 示例 |
|---|---|
| 用户动作 | 创建订单 |
| 成功定义 | 返回稳定 `order_id`，状态可读，重复请求无第二次效果 |
| 延迟 | API P95/P99 |
| 正确性 | 约束、金额、状态、幂等全部成立 |
| 可用性 | 测量窗口、排除项、错误预算 |
| 新鲜度 | 提交后 read-your-writes；事件 publish lag |
| 持久性 | 故障模型内的 RPO |
| 恢复 | 场景化 RTO，不只“启动成功” |
| 容量 | 峰值并发、增长与余量 |
| 安全 | 身份、租户、数据分类 |
| 成本 | 服务单位成本与预算 |

组件指标从这个目标推导。不要反过来因为某仪表盘有一个指标，就把它升格为
服务 SLI。

### 可用性相乘只是一种初步直觉

若一条同步路径必须经过多个独立组件，在非常简化的独立假设下：

\[
A_{path} = \prod_{i=1}^{n} A_i
\]

三个各 `99.9%` 的串行依赖约为：

\[
0.999^3 \approx 99.7003\%
\]

这提醒我们“组件都三个九”并不保证路径三个九。但不能把这个公式当精确生产
模型，因为：

- 故障往往相关，共享网络/电源/配置/身份；
- 重试、缓存和降级会改变路径；
- 读写操作依赖不同；
- 维护与区域故障不是独立伯努利事件；
- 业务正确性失败可能不表现为组件 down。

真正的目标要通过故障模型、演练和真实 SLI 验证。

### 新鲜度预算也会叠加

派生投影可能经历：

```text
transaction commit
  -> outbox polling
  -> broker publish
  -> consumer queue
  -> indexing
  -> alias visibility
```

总 lag 不是只看 broker lag。每一段要有时间戳或位置：

| 阶段 | 证据 |
|---|---|
| source commit | commit LSN / business version |
| publication intent | outbox timestamp |
| broker | partition/offset |
| consumer | acknowledged source identity |
| projection | applied version / watermark |
| query | served generation |

没有共同 identity，端到端新鲜度无法对账。

### 正确性优先于可用性包装

“失败时返回旧缓存”可能提高响应可用性，也可能把已取消订单重新显示为有效。
降级必须按数据和操作分类：

```text
public product description
  可允许有标签的短时陈旧

inventory availability
  陈旧读可能导致超卖，不能默认降级

payment state
  不能用缓存猜测

analytics dashboard
  可显示 last complete watermark
```

同一个缓存组件不能用一个统一 fallback 策略覆盖所有字段。

### SLO 目标必须与证据状态绑定

本章目录中的：

```json
"availability_target": "99.9%-proposal"
```

故意带有 `-proposal`。要去掉它，至少要经过：

```text
ch19 environment baseline
ch20 HA failure model and drills
ch21 restore evidence
ch22 endpoint and connection behavior
ch23 security controls
ch24 approved SLI/SLO and ownership
ch25 signal and alert exercise
ch26 representative capacity
```

一项数字若没有：

```text
measurement
window
population
exclusions
owner
alert
runbook
evidence retention
```

它只是愿望。

### 统一目标不等于统一部署

服务目标统一，是为了让组件协作；并不要求把组件放在同一主机、同一进程或
同一团队。

反过来，部署在同一 PostgreSQL cluster 也不自动意味着目标相同：

- 交易写入需要 read-your-writes；
- 离线分析允许 60 秒 lag；
- 备份任务关注可恢复性；
- 搜索 pilot 关注质量与构建时间。

平台要把这些 workload class 显式分开，并为冲突设优先级。

### 用一个“合同—证据—动作”闭环

每项服务能力最终应形成：

```text
contract
  owner + semantics + objectives + limits

evidence
  SQL/catalog/metric/log/drill/manifest

decision
  accepted / conditional / pilot / rejected

action
  deploy / isolate / tune / externalize / rollback

review trigger
  version / scale / incident / objective / ownership change
```

本章的四份 JSON 和一个 ADR 正是在演示这个闭环。它们的价值不在文件格式，
而在于架构结论可以被程序拒绝、被证据更新，也可以在条件变化时退出。

---

[返回本章目录](../) · [下一节：PostgreSQL 的强项与代价](../02/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
