# 服务目录与责任模型

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

---

PostgreSQL 集群是技术对象，服务是责任对象。

```text
technical object
  cluster pg-test
  members pg-test-1/2/3
  ports 5433/5434/5436/5438
  database test

service object
  pg36_shop
  journey place-order / read-order
  owner / dependency / SLO / escalation / evidence
```

一个集群可以承载多个服务；一个服务也可以依赖多个数据库、队列和外部接口。
若资产目录只记录 IP、实例和版本，事故发生时仍不知道谁能决定：

- 是否暂停下单；
- 是否允许读到旧数据；
- 是否执行 failover；
- 是否接受恢复到某个时间点；
- 是否通知受影响用户；
- 是否冻结发布；
- 谁能关闭事件。

本节先把这些决定分配给职责，再讨论服务等级、依赖与健康证明。

## 24.1.1 服务所有者、数据所有者与平台所有者 {#item-24-1-1}

### owner 不是“出问题时帮忙的人”

owner 是对某类决定最终负责的职能。它可以把任务委托给执行者，但不能把
accountability 变成“大家共同负责”。

本章采用四个稳定职能：

| 职能 | 最终负责什么 | 不应独自决定什么 |
|---|---|---|
| service owner | 用户旅程、SLO、发布优先级、业务降级 | 数据含义、底层恢复细节 |
| data owner | 数据语义、质量、保留目的、隐私分类 | 集群拓扑与数据库执行步骤 |
| platform owner | PostgreSQL/Pigsty、容量、备份恢复、平台执行 | 用户可接受的损失与业务优先级 |
| security/privacy | 访问政策、安全例外、证据披露、隐私升级 | 正常业务功能取舍 |

这四者不是四个超级管理员。理想状态恰恰相反：

```text
service owner          does not need database superuser
data owner             does not need shell access
platform owner         does not invent data meaning
security/privacy       does not run every routine migration
```

权限和责任要能分开。第 23 章已经把 login、runtime、owner 与 break-glass
角色拆开；本章把组织责任也做同样的拆分。

### service owner

service owner 负责从用户视角定义合同：

```text
journey:
  place-order

eligible:
  authorized, valid request admitted by the application

good:
  success returned and idempotency token reconciles to one committed order

degradation:
  stop new writes, preserve read-only status lookup
```

它不能只写“数据库可用”。若应用在 PostgreSQL 正常时返回 500，用户仍然
没有获得服务。

service owner 还要在错误预算耗尽时决定：

- 暂停哪些功能发布；
- 哪些可靠性工作优先；
- 是否启用产品降级；
- 哪些业务风险值得例外。

这些决定必须与 platform owner 共同评审技术可行性，但不能完全下放给 DBA。

### data owner

data owner 负责说明“一行数据代表什么”和“错误意味着什么”：

```text
order_id          是否全局唯一
tenant_id         谁能看见
amount            使用什么货币和舍入规则
status            允许哪些状态迁移
committed order   用户是否已经获得不可撤销承诺
```

恢复时，platform owner 可以证明 PostgreSQL 启动、system identifier 正确、
恢复目标已经到达；data owner 仍要判断：

- 目标时间是否符合业务容损；
- `keep` 应当存在、`discard` 应当不存在是否足够；
- 订单、库存和支付是否需要跨系统核对；
- 哪些差异要停止业务而不是继续恢复。

因此“数据库恢复成功”与“业务恢复可接受”是两个验收签名。

### platform owner

platform owner 维护的是可交付数据库服务：

- Pigsty inventory 和渲染产物；
- PostgreSQL 初始化与运行参数；
- Patroni/etcd 角色与控制面；
- HAProxy/PgBouncer 入口；
- pgBackRest/WAL 恢复路径；
- 容量、升级、安全基线；
- 指标、日志、告警与值班执行。

它必须能把平台事实翻译成服务后果。例如：

```text
pg-test-2 replay lag rises
  -> affected path: replica reads
  -> user consequence depends on read routing and freshness contract
  -> primary writes may still be healthy
  -> do not page the service owner merely because one cause metric is high
```

平台 owner 也不能用“基础设施都绿”关闭事故。关闭条件应来自用户 SLI、
数据核对与恢复验证。

### security/privacy owner

数据库治理产生敏感证据：

- 谁请求、批准和执行；
- 哪个身份获得了什么权限；
- 哪条查询失败；
- 哪个租户或事件受影响；
- 备份和日志保留多久；
- incident evidence 向谁披露。

security/privacy owner 决定访问、例外和披露边界。它不意味着把所有日志永久
保存。收集过多原始 SQL、bind value 或客户行，会让“审计系统”变成新的数据
泄露面。

### incident commander 不是第五个永久 owner

incident commander 是事件期间的协调角色：

```text
declares severity
maintains decision log
assigns technical lead
coordinates communication
checks stop conditions
decides next checkpoint
closes or hands off incident
```

它可以由 service owner 职能担任，也可以按事件规模另行指定。技术 lead
负责执行和验证，不应同时独占业务影响判断、通信和所有批准。

### 使用职能身份，不把人名写死在合同里

服务卡中的：

```json
{
  "function": "platform",
  "role_id": "database-platform-owner",
  "on_call_route": "route://db-platform-oncall"
}
```

描述稳定接口。真实系统再把 `role_id` 映射到：

- 身份目录 group；
- 当前 primary/secondary on-call；
- 可达的通知渠道；
- 时区与交接；
- 替补和 manager escalation。

只写“找小王”有三个问题：

1. 人员变化会让文档瞬间过期；
2. 不知道非工作时间找谁；
3. 个人名字不能证明权限和当班状态。

反过来，只写一个永远没人测试的 `db-oncall@example` 也不算 owner。路由必须
通过 notification canary 定期证明可达。

### 不要把 RACI 做成责任迷宫

RACI 可以帮助列出 Responsible、Accountable、Consulted、Informed，但最常见
的失败是每个格子都填满：

```text
five accountable owners
ten consulted teams
no person authorized to stop
```

对一个决定，应当尽量有一个最终 accountable function：

| 决定 | A | R | C |
|---|---|---|---|
| 修改用户 SLO | service | reliability analyst | platform/data |
| 执行 planned switchover | platform | qualified operator | service |
| 接受恢复点 | data | platform recovery lead | service/security |
| 暂停下单 | service | application operator | data/platform |
| 批准高权访问 | security/privacy | identity admin | platform/data |

“A” 不是可以跳过技术门禁的权力。service owner 可以要求恢复服务，但不能
要求 platform operator 在 fencing 未知时强制 promote。

### 所有权的验收问题

每个 owner 都应回答三类问题：

```text
decision
  我能批准或拒绝什么？

evidence
  我需要看见哪些事实？

absence
  我不在时由哪条已测试路径接替？
```

若答案只有“出了事群里讨论”，责任模型尚未建立。

## 24.1.2 等级、规格、依赖、值班与升级路径 {#item-24-1-2}

### 服务等级是一组义务

`gold`、`silver`、`bronze` 如果没有义务，只是颜色：

```text
Gold PostgreSQL
  ? on-call coverage
  ? SLO
  ? restore frequency
  ? RTO/RPO
  ? upgrade deadline
  ? security review
  ? capacity headroom
  ? evidence retention
```

可执行的 tier 应当打包：

| 维度 | 需要固定的内容 |
|---|---|
| availability | 用户 SLI、目标、窗口、预算政策 |
| recovery | 恢复类型、证据新鲜度、演练频率 |
| support | 值班时段、确认目标、升级链 |
| change | 风险级别、批准、窗口、冻结规则 |
| capacity | headroom、预测周期、扩容 lead time |
| security | 认证、加密、轮换、审计与例外 |
| lifecycle | PostgreSQL/Pigsty 版本和升级时限 |
| evidence | 保留期、访问、完整性与删除 |

本章的 `sandbox-reference` 明确写：

```text
production tier        false
on-call                illustrative route only
capacity               no laptop sizing inference
restore                one retained sandbox run
change                 L2/L3 independent authority
security               mechanism evidence, production gaps retained
```

这比给沙箱贴上“Silver”诚实得多。

### 规格不是只有 CPU 和内存

数据库 service specification 至少有四组：

#### 用户规格

- 关键旅程；
- 可用性、延迟、正确性、新鲜度；
- 写后读、一致性和降级行为；
- 支持时段和沟通。

#### 数据规格

- 数据分类；
- 主权和保留；
- 容许丢失和恢复粒度；
- tenant/authorization 边界；
- 核对不变量。

#### 技术规格

- PostgreSQL major 与扩展；
- 拓扑、同步策略、入口；
- pool mode 与连接预算；
- 备份、WAL 和恢复设施；
- 监控、日志和依赖。

#### 运营规格

- owner/on-call；
- SOP 和演练；
- 变更冻结；
- 证据与审计；
- 已接受例外和到期日。

只记录 `4C16G500G`，不能回答任何恢复、安全和服务问题。

### 依赖必须写 failure semantics

依赖表不能止于“uses PostgreSQL”：

| 依赖 | 关系 | 失败语义 |
|---|---|---|
| application runtime | 发出用户事件、提交事务 | PG 健康时应用仍可拒绝或错误提交 |
| HAProxy/PgBouncer | 承担入口与池化 | 直连主库成功不代表应用入口成功 |
| PostgreSQL | 事务与持久状态 | 连接成功不代表业务事务正确 |
| Patroni/etcd | 角色和切换控制 | 控制面受损可先阻止安全切换 |
| pgBackRest/WAL | 恢复路径 | backup exists 不代表可恢复 |
| Victoria stack | 观察和告警 | telemetry missing 会把状态变成 unknown |

failure semantics 回答：

```text
dependency fails
  -> which journey is affected?
  -> fail closed, fail open, degrade, or become unknown?
  -> who owns first diagnosis?
  -> what is the first safe action?
```

例如 etcd 暂时不可用时，现任 primary 可能继续服务；真正受损的是安全 role
change 能力。若把“etcd member down”直接等同于用户 outage，会制造误报；
若完全忽略，又会在需要 failover 时才发现控制面失效。

### 把共享故障域写进依赖

第 19 章的四台 VM 看起来有四个地址，但共享：

- 一台 laptop；
- 一个 hypervisor；
- 一套供电和网络；
- 同一底层存储；
- 单节点 etcd；
- 单一 MinIO/control host。

因此拓扑图中的三节点不能推出三个独立 failure domain。依赖模型应同时表达：

```text
logical redundancy = 3 PostgreSQL members
physical independence = not established
```

这也是为什么服务卡保留 known production gaps，而不是把它们埋在实验日志里。

### 值班路径需要输入与输出合同

一条值班 route 至少固定：

```text
input
  severity
  service id
  user impact
  start time
  alert id
  dashboard
  runbook

output
  acknowledged by whom
  incident id
  technical lead
  next update time
  decision log
```

确认时限不是修复时限：

```text
acknowledge 5 minutes
  != resolve 5 minutes
  != RTO 5 minutes
```

`ack` 只证明有人接管。恢复目标必须按故障类别、数据边界和服务合同另行定义。

### escalation 不是把同一消息抄送更多人

有效升级改变权力、资源或沟通：

```text
SEV-2
  service on-call + platform on-call
  bounded user degradation

SEV-1
  incident commander
  data owner for correctness/recovery decision
  security/privacy for breach or evidence handling
  executive/customer communication when required
```

升级触发可以来自：

- 影响范围扩大；
- 数据正确性或保密性受威胁；
- 错误预算快速燃烧；
- 操作超过停止线；
- recovery path 不确定；
- 事件超过时间阈值；
- 现有 authority 不足。

不要只按“CPU 超过 90%”升级。CPU 是原因候选，不是业务严重度。

### 依赖与升级的服务卡片段

本章完整文件见
[`service-card.json`](/labs/ch24/service-card.json)。其中一项依赖：

```json
{
  "id": "pgbackrest-repository",
  "kind": "recovery-system",
  "relationship": "stores base backup and WAL needed for recovery",
  "failure_semantics": "a recent backup job does not prove the selected recovery point is usable",
  "owner_function": "platform"
}
```

`owner_function` 负责第一技术响应；最终恢复点是否可接受仍需要 data/service
owner。

### 服务目录的最小字段

```text
identity
  service_id / display_name / environment / catalog_status

purpose
  business capability / customer journeys / critical operations

scope
  data class / production boundary / database / cluster / entrypoints

ownership
  service / data / platform / security

tier
  explicit obligations

dependency
  relation / failure semantics / owner

health
  user contract and component layers

escalation
  severity / trigger / role / acknowledgement

governance
  SLO policy / SOP catalog / evidence policy / known gaps
```

每次更改服务卡，应检查对应的 SLO、告警 route、SOP 与权限是否一起更新。

## 24.1.3 实例健康不等于业务服务健康 {#item-24-1-3}

### 六层证明

本章使用六层健康链：

```text
1 process
  expected daemon exists

2 endpoint
  declared client path is reachable and routes correctly

3 authentication
  intended identity over intended transport can authenticate

4 transaction
  intended operation can commit and its outcome can be reconciled

5 correctness
  domain, tenant and authorization invariants hold

6 durability/recovery
  acknowledged state survives declared failures and can be restored
```

没有任何一层单独足够。服务健康还必须满足用户合同。

### process alive 只证明进程事实

`systemctl is-active postgresql` 或 exporter 的 up 指标能证明采集时进程存在。
它不能证明：

- postmaster 接受连接；
- 监听的是正确地址；
- HBA 允许目标身份；
- server 是正确集群；
- 当前节点可写；
- 磁盘还有空间；
- transaction 能提交；
- 应用 SQL 正确。

进程指标适合组件诊断和自动重启，不应直接成为业务 availability 分子。

### `pg_isready` 也有边界

`pg_isready` 检查 PostgreSQL server 的连接状态，返回 accepting、rejecting
或 no response。它不需要提供正确用户名、密码或数据库才能获得 server
status；错误参数还可能在服务器日志留下失败连接。官方边界见
[pg_isready](https://www.postgresql.org/docs/18/app-pg-isready.html)。

因此：

```text
pg_isready success
  -> a PostgreSQL server at this endpoint is accepting connection attempts

not proven
  -> intended application can authenticate
  -> intended database and role are correct
  -> intended transaction succeeds
```

### `SELECT 1` 证明得更多，但仍不够

使用真实入口、真实 TLS 约束、合成身份执行：

```sql
SELECT
  current_database(),
  session_user,
  current_user,
  pg_is_in_recovery(),
  current_setting('server_version');
```

能证明：

- 网络和 PostgreSQL 协议；
- 认证；
- database/role 身份；
- 当前节点 recovery 状态；
- 简单 query path。

它仍然绕过：

- 业务表；
- lock/constraint/RLS；
- write permission；
- commit；
- connection pool session semantics；
- 应用序列化和错误映射。

### transaction probe 必须处理未知结果

更接近用户的 probe：

```text
generate unique synthetic token
  -> call declared application or database write path
      -> commit
          -> query token through declared result path
              -> classify exactly one of:
                   committed once
                   absent
                   duplicate
                   unknown
```

连接在 `COMMIT` 附近断开时，客户端可能不知道事务是否已经提交。直接重试会
造成重复。probe 和 Runbook 都必须使用幂等 token 核对结果。

第 20、22 章的切换演练正是按：

```text
acknowledged
unknown committed
unknown absent
duplicate
unreconciled
```

分类，而不是把所有连接错误都计为“数据库没写入”。

### correctness 不能由事务成功代替

下面都可能 commit 成功：

- 订单写到了错误 tenant；
- 金额舍入错；
- 同一个 idempotency token 生成两张订单；
- RLS context 泄漏；
- 状态从 `cancelled` 跳回 `paid`；
- 应用返回失败但后台已提交。

所以 good availability event 必须包含结果核对，correctness control 还要独立
运行领域 reconciliation。

PostgreSQL constraint、unique index、foreign key、RLS 与 transaction isolation
是防线，不是所有业务正确性的完整证明。

### replica “时间延迟”会在空闲时误导

常见查询：

```sql
SELECT now() - pg_last_xact_replay_timestamp();
```

在没有新 WAL 的空闲系统中，这个差值会持续增大，即使 replica 已完全追平。
它回答“最后一个已 replay transaction 的时间距现在多久”，不自动等于当前
replication lag。

对写后读新鲜度，应使用：

```text
commit-correlated token
  write through declared write path
  record commit outcome
  poll declared read path
  measure visibility bound
```

诊断时再结合：

- primary 与 replica WAL LSN distance；
- `pg_stat_replication`；
- `pg_stat_wal_receiver`；
- replay pause/conflict；
- route 选择；
- 客户端时钟。

PostgreSQL 18 的统计视图目录见
[Monitoring Database Activity](https://www.postgresql.org/docs/18/monitoring.html)。

### cluster healthy 也不等于 entrypoint healthy

可能出现：

```text
Patroni:
  one leader + two streaming replicas

but:
  HAProxy still routes old leader
  PgBouncer holds stale server connections
  DNS/VIP is unreachable from application network
  client certificate name mismatches
  pool user is absent
```

因此健康检查要从用户网络和声明入口执行。直接 SSH 到 primary 后 `psql` 成功
只能作为绕过入口的诊断事实。

### Pigsty 提供组件视角，不替应用发明 good event

Pigsty 能关联：

| 层 | 典型事实 |
|---|---|
| host | CPU、memory、disk、network、kernel |
| PostgreSQL | activity、transactions、locks、WAL、I/O |
| PgBouncer | clients、servers、wait、pool |
| HAProxy | frontend/backend/session/health |
| Patroni/etcd | role、timeline、control state |
| pgBackRest | backup/check/log |
| observability | scrape、rule evaluation、notification |

`cls`、`ins`、`ip` 让这些事实能关联到同一个 Pigsty 对象。应用仍需要提供：

- operation class；
- eligible/good outcome；
- user latency；
- commit token；
- domain reconciliation；
- service/environment。

不要在时序标签里放 `order_id`、`tenant_id`、完整 SQL 或 error message。它们会
造成无界 cardinality，也可能泄露数据。

### 三个判断练习

#### 情形 A：一台 replica down，用户指标正常

```text
user availability       healthy
freshness               healthy
remaining redundancy    degraded
```

处理：

- 不必重复 page 所有业务 owner；
- platform ticket 或受控事件；
- 检查另一 replica、WAL 保留与恢复能力；
- 若 redundancy policy 已触及耐久性停止线，可升级。

#### 情形 B：三台 PostgreSQL 都绿，下单 100% 失败

```text
database component      healthy
service availability    failing
```

处理：

- page service symptom；
- 检查应用 release、identity、pool entry、schema compatibility；
- 不要因为 PG dashboard 绿色而关闭；
- platform 指标用于定位原因。

#### 情形 C：请求 99.999% 成功，但出现一条跨租户数据

```text
ratio availability      excellent
correctness control     failed
severity                potentially SEV-1
```

处理：

- 冻结相关写入；
- 保留 reconciliation boundary；
- security/privacy 与 data owner 加入；
- 不能用充足错误预算继续发布。

### 健康矩阵

| 观测 | 可以证明 | 不能证明 |
|---|---|---|
| process active | daemon 存在 | endpoint/auth/transaction |
| `pg_isready` | server 接受连接尝试 | 身份、业务 SQL |
| authenticated `SELECT 1` | 入口、认证、query | write/commit/correctness |
| write token visible | 某次事务与可见性 | 全量业务正确 |
| reconciliation zero | 已覆盖不变量未见差异 | 未覆盖规则、未来恢复 |
| restore drill | 某份证据下可恢复 | 所有故障、生产 RTO |
| user-event SLI | 窗口内用户结果 | 根因 |

这张表说明为什么需要两套视角：

```text
SLI tells whether users are hurt
component telemetry tells why and what to do
```

### 服务卡审查清单

- [ ] `service_id` 是否稳定且不等于某台机器？
- [ ] 是否列出关键 user journey 与 operation class？
- [ ] service/data/platform/security 是否各有明确 accountability？
- [ ] owner 是否映射到可测试 route，而不是个人名字？
- [ ] tier 是否列义务，而不是颜色？
- [ ] 每个依赖是否写 failure semantics 和 owner？
- [ ] logical redundancy 与 physical failure domain 是否分开？
- [ ] severity 是否来自用户、数据和恢复后果？
- [ ] instance、cluster、endpoint 和 service health 是否分开？
- [ ] synthetic probe 是否使用真实入口、身份和业务路径？
- [ ] commit 附近的 unknown outcome 是否可核对？
- [ ] correctness 和 durability 是否有独立 control？
- [ ] known production gaps 是否在服务卡而非某个聊天记录里？

完成这张服务卡后，下一节才能计算 SLO。否则百分比没有分子、分母、责任人或
决策后果。

---

[返回本章目录](../) · [下一节：SLI、SLO 与错误预算](../02/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
