# 先写服务需求

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

---

部署的第一行不应是：

```yaml
pg_version: 18
```

而应是：

```text
这个服务为何存在？
什么数据不能丢？
什么操作不能错？
谁承担结果？
失败多久开始造成不可接受损失？
```

如果这些问题没有答案，CPU、节点数、同步复制和备份频率都只能靠猜。

## 19.1.1 业务重要性、数据分类与所有者 {#item-19-1-1}

### 先定义业务能力

`pg36_shop` 提供的不是“一个 database”，而是：

```text
商品浏览与检索
客户身份与地址
订单创建和状态转换
库存与支付事实
配送时空事件
分析与外部投影源
```

不同能力的故障损失不同：

| 能力 | 失败影响 | 可接受降级 |
|---|---|---|
| 创建订单 | 直接收入/履约风险 | 拒绝比重复创建安全 |
| 查询支付状态 | 财务与客服风险 | 不可用旧缓存猜测 |
| 商品描述 | 转化下降 | 可短时有标签陈旧 |
| 搜索排序 | 发现能力下降 | 可回退简单检索 |
| 分析报表 | 决策延迟 | 可显示 last watermark |
| 图片读取 | 体验下降 | placeholder/重试 |

一个统一 database 可以承载它们，但 service objective 不能只写一个“重要”。

### 业务重要性需要损失模型

常用分级：

```text
tier 0 / critical
  停止即产生重大安全、财务或合规损失

tier 1 / important
  核心业务中断，短时可人工/降级

tier 2 / standard
  影响明显，但可在较长窗口恢复

tier 3 / development
  无生产承诺，可重建
```

分级必须连接动作：

| 项 | critical | standard | development |
|---|---|---|---|
| owner/on-call | 24×7 明确 | 支持窗口明确 | 工作时间 |
| HA | 多故障域并演练 | 按目标设计 | 可无 |
| backup | 跨域、频繁演练 | 标准恢复 | 可选且声明 |
| change | 强审批/回退 | 标准流程 | 自助边界 |
| capacity | 高 headroom | 正常 headroom | best effort |
| incident | 快速升级 | 标准升级 | issue |

若分级只改变标签颜色，就没有价值。

### 数据分类决定安全与恢复边界

分类至少包含：

```text
public
internal
confidential
restricted / regulated
credentials and cryptographic material
```

对每类记录：

- 数据 owner；
- 合法用途；
- 可访问角色；
- 是否可复制到 dev/test；
- 加密与审计要求；
- 地域限制；
- retention；
- 删除时限；
- backup 中如何处理；
- incident 通知义务。

本章正式 sandbox 只允许：

```text
synthetic teaching data
production_data_permitted=false
production_traffic_permitted=false
```

这是硬边界。VM 看起来像生产拓扑，也不能把生产数据“临时导入测试”。

### 数据 owner 与平台 owner 不同

建议至少区分：

| 角色 | 责任 |
|---|---|
| business/data owner | 数据用途、正确性、分类、保留、损失 |
| service owner | SLO、错误预算、依赖、发布、事件 |
| database platform | PostgreSQL/Pigsty、HA、备份、接入、容量 |
| security/privacy | 威胁、控制、审计、合规 |
| application team | schema/SQL、连接、重试、兼容、流量 |
| incident commander | 事件期协调和决策 |

一个人可以兼任，责任不能消失。

### owner 要能作决定

在事故中，DBA 可以说：

```text
“当前恢复到 10:31 会丢 47 秒提交，
 恢复到最新可能保留错误写入。”
```

但通常不能独自决定哪种业务损失更小。需要提前指定有权选择：

```text
availability versus consistency
restore point
degraded operation
data deletion
customer communication
error-budget spend
```

“负责人：数据库团队”太宽泛。最终要映射到值班角色、升级路径和替代人。

### 数据清单要包含派生副本

第 18 章已经定义：

```text
PostgreSQL authoritative business state
cache projection
event delivery/replay log
object bytes
search projection
analytical projection
backup/WAL copies
```

部署规划不能只列 primary 数据目录。数据流 inventory 应记录：

| 副本 | authority | freshness | retention | deletion | rebuild |
|---|---|---|---|---|---|
| replica | PostgreSQL | replay lag | current | follows WAL | base backup |
| backup | historical recovery | backup/WAL | policy | retention expiry | restore |
| cache | derived | TTL/version | eviction | tombstone/TTL | source refill |
| event bus | delivery log | publish lag | broker policy | workflow | replay/snapshot |
| lake/search | projection | watermark | generation | tombstone | rebuild |

这些副本会影响存储、网络、密钥和故障域规划。

### 分类要进入 inventory 与 evidence，但不要泄密

配置可保存：

```yaml
service_class: pg-ha-standard
data_class: confidential
owner: shop-order-team
```

不应把：

```text
customer data samples
passwords
private keys
recovery secrets
token values
```

写入基线报告。

本章的 inventory projection 只输出安全 allowlist，记录：

```text
source mode
secret-bearing source fingerprint withheld
safe projection sha256
secret fields redacted count
secret values exported = 0
```

它证明审计过程知道 secret 存在，同时不把值复制到 evidence。

### 未知 owner 是阻断项

没有 owner 的 service 不应进入生产。原因很现实：

- 谁批准 maintenance？
- 谁判断数据正确？
- 谁接 incident 电话？
- 谁接受 RPO？
- 谁决定退役？
- 谁支付容量？

可以在 sandbox 用 placeholder，但 production gate 必须把 placeholder 映射到
真实责任人/组和升级路径。

### `pg36_shop` 的当前需求身份

本章
[`requirements.json`](/labs/ch19/requirements.json)
写的是：

```text
service=pg36_shop
data=synthetic teaching only
business owner=placeholder
platform owner=placeholder
target=disposable local Linux sandbox
production approval=false
```

所以它可以验收部署流程，不能通过真实 `pg-ha-standard` 的 owner/data gate。

## 19.1.2 负载形态、增长、峰谷和批处理窗口 {#item-19-1-2}

### “OLTP”不是容量需求

同为 OLTP，可能分别是：

```text
10k tiny point reads/s
500 write transactions/s with 20 indexes
50 large JSON updates/s
100 concurrent long business transactions
bursty checkout traffic
multi-tenant mixed workload
```

需要一份 workload inventory：

| 维度 | 例 |
|---|---|
| transaction classes | browse/order/pay/admin |
| read/write ratio | 分类别 |
| statements/transaction | 分布 |
| rows touched | P50/P95/P99 |
| connection behavior | pool、active、idle |
| latency | P50/P95/P99/timeout |
| concurrency | arrival、active、parallel |
| WAL | bytes/s 与 burst |
| temp | bytes/query 与 aggregate |
| locks | wait、deadlock、long xact |
| maintenance | vacuum/index/backup |

第 26 章才正式做容量曲线，本章先确保环境规划有输入。

### 交易、搜索、空间与分析分开建模

`pg36_shop` 的四类负载形状：

```text
transactional
  selective, short, latency-sensitive, correctness first

search
  rank/filter, index-heavy, quality + latency

spatiotemporal
  GiST, range, event ingest, retention

analytics
  large scan/aggregate, temp/parallel, freshness-tolerant
```

如果只用总 QPS，分析扫描和订单写入会互相隐藏。

每类要记录：

```text
endpoint/role
timeout
resource priority
expected concurrency
allowed replica/freshness
degradation
owner
```

### 峰值不是日均乘一个系数

峰值来源：

```text
营销活动
整点任务
工资/账单周期
客户端 retry
故障流量转移
缓存失效
批处理重跑
schema migration
backup/checkpoint overlap
```

真实 peak envelope 至少有：

```text
amplitude
duration
ramp rate
frequency
correlated workloads
recovery tail
```

一分钟 10 倍峰值与持续 4 小时 3 倍峰值需要不同资源和降级。

### 流量转移后的单节点峰值

三节点 topology 正常时，读流量可分散；一台 replica 故障后：

\[
load_{remaining}
=
\frac{total\ eligible\ load}{remaining\ eligible\ capacity}
\]

若两个 replica 平时各 50%，失去一个后另一个可能接近 100%，也可能 fallback
到 primary。容量必须按 failure state 规划，不只按 happy path。

同理，failover 后：

- 新 primary 承接全部写；
- cache 冷；
- client 重连；
- replica 重新追赶；
- backup/maintenance 可能仍在；
- 旧 primary 需要重建。

### 增长要分逻辑与物理

逻辑增长：

```text
customers
products
orders/day
items/order
events/day
tenants
retention days
```

物理增长：

```text
heap
TOAST
indexes
dead tuples/bloat
WAL
temp peak
replicas
backup full/diff/incr
archive retention
monitoring/logs
external projections
```

一个粗略存储模型：

\[
capacity =
(heap + toast + indexes + free\ space)
\times replica\ factor
+ backup/archive
+ maintenance/upgrade\ headroom
\]

不能直接拿业务 CSV 大小乘副本数。

### 使用增长曲线，不只用线性外推

记录：

```text
daily/weekly sample
seasonality
new feature step changes
largest tenant
retention changes
index additions
compression/archival
confidence interval
```

容量到达时间：

\[
T_{exhaust}
=
\frac{usable\ capacity - current\ usage - required\ headroom}
{growth\ rate}
\]

增长率有区间时给出 earliest/expected，而不是一个虚假精确日期。

### 批处理窗口是一项共享资源预约

批任务包括：

```text
ETL/export
materialized refresh
search/vector rebuild
backup
VACUUM/ANALYZE
index build
partition lifecycle
financial close
data quality reconciliation
```

每项保存：

| 字段 | 意义 |
|---|---|
| earliest start / deadline | 可运行窗口 |
| duration distribution | 不只平均 |
| CPU/I/O/WAL/temp | 资源 |
| locks/snapshot | 并发影响 |
| retry | 是否会叠加 |
| freshness | 延迟后果 |
| owner | 谁停止/恢复 |
| conflict priority | 与交易冲突时谁让路 |

“晚上跑”不是窗口。跨时区业务可能没有真正夜间。

### 维护和业务峰值要画在同一时间轴

建议按 UTC 画一周：

```text
online traffic
batch
backup
checkpoint/WAL archive
autovacuum debt
reporting
deploy
on-call coverage
```

你可能发现：

```text
业务低谷
  = backup full
  = ETL full scan
  = index maintenance
  = replica lag peak
```

所谓低谷实际上是数据库最忙时段。

### 负载可回放性

为了比较环境，保存：

```text
schema/version
data generator or anonymized snapshot identity
query fingerprints
parameter/selectivity distribution
arrival model
connection/pool model
background jobs
warm/cold cache protocol
duration
random seed
success and correctness golden
```

只保存一条 `pgbench -c 100` 命令不足以代表业务。

### sandbox 的资源结论边界

本章为 Vagrant sandbox 建议每台至少：

```text
>= 2 logical CPUs
>= 2 GiB memory
>= 8 GiB root free
swap = 0
```

这些只是教学环境的建议下限，不是 `pg36_shop` 的生产容量规格。正式实验中，
三台 `pg-test` VM 只有 1 个 vCPU、约 1.9 GiB 内存；部署虽然完成，但必须以
`EX19-LAB-RESOURCE-FLOOR` 记录偏差，不能把成功运行反推为资源充足。

四台 VM 共享一台 laptop 的：

```text
CPU
memory controller
physical storage
power
hypervisor
host network
```

因此任何 benchmark 都不能外推生产。

### 需求表中的 unknown

本章刻意不为以下项目造数字：

```text
production QPS
production data growth
production storage latency
production connection budget
production batch window
```

它们进入第 24、26、27 章。部署基线的职责是让 unknown 可见，并阻止默认值被
误报为需求。

## 19.1.3 可用性、RPO、RTO 与维护窗口 {#item-19-1-3}

### 四个概念先分开

```text
availability
  服务在测量窗口内按定义成功的比例

RPO
  可接受的数据恢复点损失

RTO
  从场景发生到服务恢复到规定状态的时间

maintenance window
  允许计划变更及其用户影响的时间边界
```

它们相关，但不能互相替代。

三节点自动 failover 可能有较短可用性中断，却无法恢复昨天误删的数据；一天
一次 full backup 可能可恢复，却不提供当前 primary HA。

### 先定义成功请求

可用性分母和成功必须明确：

```text
哪些 endpoint
哪些 operation
哪些用户/区域
什么状态码/SQLSTATE
正确性是否计入
延迟阈值
陈旧度阈值
测量位置
计划维护是否排除
```

如果数据库返回 200/row 但金额错误，不应算可用。

### “几个九”换算为时间只是直觉

以 30 天窗口为例：

| 目标 | 粗略不可用预算 |
|---:|---:|
| 99% | 7h 12m |
| 99.9% | 43m 12s |
| 99.95% | 21m 36s |
| 99.99% | 4m 19s |

真正 SLO 仍由事件型 SLI 计算，不能只用服务器 uptime。

### RPO 必须绑定故障场景

示例：

```text
single replica loss       RPO 0
primary failover          async lag within measured policy
sync-confirmed commit     RPO 0 only in modeled sync failure domain
operator DROP             PITR target before error
storage corruption        last verified clean recovery point
region loss               cross-domain repository/standby position
```

“RPO=0”若没有场景和确认语义，就是不完整承诺。

应用还要知道：

```text
client got success -> commit durability promise
client got timeout -> outcome unknown, must query by idempotency key
```

### RTO 从开始点到结束点

RTO 的起点可能是：

```text
physical failure
monitor detects
alert reaches human
incident declared
recovery decision
```

终点可能是：

```text
database accepts connections
write service healthy
business golden passes
backlog caught up
all clients restored
```

如果不定义，两个团队报告的 RTO 可以相差整个检测与验证阶段。

建议分解：

\[
RTO =
detection
+ decision
+ execution
+ validation
+ traffic\ restoration
\]

每段都能优化，也都可能失败。

### HA RTO 与 restore RTO 不同

| 场景 | 路径 |
|---|---|
| primary host loss | detect → elect → promote → route → client retry |
| database deleted | stop damage → choose target → restore → replay → validate |
| corrupt pages | preserve evidence → classify → restore/rebuild → validate |
| region loss | activate remote infra → restore/promote → dependencies → DNS |

不要用 Patroni failover 的秒数回答整库恢复需要多久。

### maintenance 是预算，不是免责

维护窗口应记录：

```text
frequency
duration
notice
allowed impact
rollback deadline
business blackout dates
owner approval
post-check
```

即使计划维护被 SLO 排除，用户损失仍存在。高成熟度平台会：

- 滚动维护；
- 验证连接恢复；
- 限制每次 blast radius；
- 保留 rollback；
- 记录实际中断；
- 复审窗口是否足够。

### 升级窗口必须包含回退判断

不只计算安装时间：

```text
preflight
backup/recovery point
traffic drain
package/schema change
restart/failover
application golden
observation
rollback or forward decision
```

有些 PostgreSQL major upgrade 在数据目录切换后没有简单 rollback；最后可逆
点必须写清。第 30 章专门演练。

### 服务目标从损失与成本共同推导

目标越强，通常需要：

```text
more independent replicas
synchronous distance/latency trade-off
more recovery copies
more frequent drills
more on-call coverage
more capacity headroom
more change discipline
```

不能只问“技术上能否做到”，还要问业务是否愿意持续支付，以及组织能否操作。

### 本章不通过第 20/21 章

第 19 章只验证环境前提：

```text
hosts distinct
versions/initialization uniform
declared and live topology agree
endpoints exist
roles/services active
exceptions explicit
```

它不注入故障，不执行 failover，不做 restore。因此：

```text
ch20-ha = pending
ch21-backup-restore = pending
```

### sandbox 的准确结论

本章四节点能证明：

```text
one pg-meta member
three pg-test members
one live leader
two live replicas
one offline-query declaration
Pigsty inventory/host/Patroni/SQL facts agree
```

不能证明：

```text
four production failure domains
production RPO/RTO
production storage durability
production capacity
production secret lifecycle
```

因为所有 VM 共享物理 laptop，etcd 与 backup target 也各只有一个控制节点。

### 把例外当结构化结果

[`requirements.json`](/labs/ch19/requirements.json)
固定六项 sandbox exception：

```text
EX19-SHARED-HYPERVISOR
EX19-SINGLE-ETCD
EX19-SINGLE-BACKUP-TARGET
EX19-VIRTUAL-STORAGE
EX19-INVENTORY-SECRETS
EX19-LAB-RESOURCE-FLOOR
```

通过结果必须写：

```text
sandbox_l2=accepted-with-exceptions
production_ch19_gate=pending
```

“验收通过”后面没有范围，是一种危险省略。

---

[返回本章目录](../) · [下一节：计算、内存、存储与网络](../02/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
