跳转到主要内容

24 纲举目张:SLO、SOP 与组织治理

数据库平台最危险的状态,不是“什么都没有”,而是看起来什么都有:

三节点
有备份
有监控
有告警
有值班群
有操作文档

却没人能回答:

用户究竟获得什么服务?
什么事件才算成功?
一个实例宕机是否已经影响用户?
数据错了一行,能否被 99.9% 的平均值原谅?
计划维护为什么可以从报表中消失?
告警响起后的第一个安全动作是什么?
谁能批准切换,谁能执行,谁负责停止?
“备份成功”以外,恢复过吗?
哪份证据证明这次操作的目标、输入与结果?

本章把这些问题组织成一条可执行的治理链:

用户旅程
  -> 服务目录与责任人
      -> SLI / SLO / 控制目标
          -> 错误预算与变更政策
              -> 观察与告警契约
                  -> SOP / Runbook / Drill / Change Plan
                      -> 权限、停止线与回退
                          -> 证据、审计与保留
                              -> 自动验证与对抗性反例

治理不是在 PostgreSQL 外面增加一层审批表。它的任务是把“正常”“异常” 和“谁有权改变什么”变成可计算、可否证、可追责的服务合同。

本章目标

完成本章后,你应当能够:

  1. 区分数据库实例、集群、入口、应用和用户服务;
  2. 为服务、数据、平台与安全隐私分别指定 accountable owner;
  3. 用义务而不是“金银铜”标签定义服务等级;
  4. 画出依赖、失效语义、值班与升级路径;
  5. 解释为什么 postgres_up = 1 不是业务可用性;
  6. 把 SLI 写成 good events / eligible events
  7. 区分 SLI、SLO、SLA、错误预算和控制目标;
  8. 为可用性、延迟、新鲜度选择靠近用户的测量点;
  9. 把正确性和恢复就绪从可平均的错误预算中分离;
  10. 计算事件预算、等价时间预算和 burn rate;
  11. 设计滚动窗口、低流量策略与不可追溯篡改的排除项;
  12. 用错误预算真实约束发布节奏,而不是装饰仪表盘;
  13. 区分 SOP、故障 Runbook、恢复演练和单次 Change Plan;
  14. 把变更拆成申请、评审、执行、验证、回退或前滚与关单;
  15. 为 L2/L3 高风险动作设计独立批准、精确目标和停止线;
  16. 解释双人控制、延迟确认与 break-glass 的不同作用;
  17. 为每个 SLI 固定数据源、查询、维度与缺失语义;
  18. 区分页型症状、诊断原因和容量工单;
  19. 使用 multiwindow、multi-burn-rate 作为可调起点;
  20. 让每个 page 绑定用户影响、所有者、Runbook 和首个安全动作;
  21. 监控监控系统自身,而不把“没有数据”解释为健康;
  22. 为配置、变更、访问、恢复、SLO 与事件建立证据目录;
  23. 区分普通日志、审计记录、决策证据与合规结论;
  24. 用哈希、时间、身份、最小化和保留政策建立证据链;
  25. 自动检查治理不变量,并用反例证明检查真正会失败;
  26. 对一个环境给出“合同通过、生产待决”的诚实结论。

本章不做什么

本章不会把组织设计冒充 PostgreSQL 参数调优,也不会:

  • 承诺一个普适的 99.99%;
  • 用书中的角色标识代替真实值班表;
  • 把沙箱一次成功切换称为生产 RTO;
  • 把备份任务退出码称为恢复证明;
  • 把现成组件指标直接拼成业务 SLO;
  • 发布真实告警、发送真实 page;
  • 以“合规”为名收集密码、密钥或完整客户行;
  • 让审批替代技术停止线;
  • 让自动化替代业务和风险授权。

pg36_shop 在本章仍是 synthetic teaching service。数值是可计算的政策输入, 不是与真实业务 owner 谈判后的生产承诺。

前置与后续

本章收束第 19–23 章已经获得的事实:

这些章节证明了机制,但机制不会自动成为服务承诺。例如:

ch20 observed write gap       != production RTO
ch21 one successful restore   != ongoing recoverability
ch22 pool capacity sample     != production concurrency limit
ch23 RLS mechanism passes     != organization has approved data policy

下一章 第 25 章 监控体系与可观测诊断 将实现本章输出的 指标和规则。先定语义、后写查询,是两章之间最重要的边界。

一张图看完整合同

service card
  service / data / platform / security owner
  user journeys / dependency / tier obligations
  escalation / known gaps
        |
        v
SLO policy
  eligible event / good event / measurement point
  target / rolling window / exclusion
  error-budget consequence
        |
        v
observation contract
  source / query / labels / missing semantics / fallback
        |
        +------------------------+
        |                        |
        v                        v
symptom & integrity alerts       component telemetry
page / ticket / runbook          PG / pool / host / control plane
        |                        |
        +------------+-----------+
                     v
SOP / runbook / drill / change plan
  authority / target / stop line / verify / rollback-or-roll-forward
                     |
                     v
evidence manifest
  source / collector / time / target / hash / decision / retention

任何断点都会制造假治理:

断点 表面现象 真正风险
无服务卡 大量组件指标 不知道为谁服务、谁决策
无 SLI 语义 有阈值 不知道分子、分母和缺失意味着什么
无预算政策 有 SLO 图 可靠性结果不影响发布决策
无动作合同 有 page 值班只能临场猜测
无停止线 有 SOP 文档只会推动动作,不能阻止事故
无验证 命令成功 状态是否正确仍未知
无证据边界 日志很多 不能证明目标、授权与结果,还可能泄密

五种容易混淆的对象

服务目录项

描述“谁向谁提供什么能力,以及依赖、等级和责任”。它不是机器清单。

SLI

一个实际测量值。例如:

$$ \text{availability SLI}

\frac{\text{good eligible order attempts}} {\text{all eligible order attempts}} $$

SLO

对某个窗口内 SLI 的目标。例如“滚动 28 天内至少 99.9%”。它不是法律赔偿 条款;后者通常属于 SLA。

控制目标

不适合用平均错误率淡化的要求。例如“没有未解释的账实不符”与“90 天内有 一次通过的隔离恢复”。一次数据串租不能因为本月另有一千万次正确读取而变得 可以接受。

错误预算政策

当可靠性好或差时,组织具体改变什么。没有后果的错误预算只是 KPI:

healthy      -> 正常评审节奏
watch        -> 减少并行高风险变更
constrained  -> 暂停非必要高风险发布,例外需共同批准
exhausted    -> 冻结非紧急高风险变更,优先修复可靠性

本章采用的目标

本章服务卡定义五个目标:

ID 类型 目标
SLO-AVAILABILITY ratio SLO 被接纳的下单尝试得到可核对的成功结果
SLO-LATENCY ratio SLO 被接纳的下单尝试在 250 ms 内完成
SLO-FRESHNESS ratio SLO 带已知 commit token 的读取在 5 s 内可见
CTRL-CORRECTNESS control 无未解释的重复、串租、金额或状态错误
CTRL-RESTORE-READINESS control 90 天内有通过的隔离恢复证据

它们有意不使用:

PostgreSQL process is running
Patroni reports one leader
HAProxy backend is UP
replica replay timestamp looks recent
backup job exited zero

这些都是有价值的组件事实,但只能解释服务为什么好或坏,不能单独回答用户 是否获得了正确服务。

可计算的错误预算

可用性目标为 99.9%,滚动窗口为 28 天:

allowed bad ratio=10.999=0.001 \text{allowed bad ratio} = 1 - 0.999 = 0.001

若窗口内有 10,000,000 个 eligible events:

event budget=10,000,000×0.001=10,000 \text{event budget} = 10{,}000{,}000 \times 0.001 = 10{,}000

若为了直觉把比例换算成连续时间:

28×24×60×0.001=40.32 minutes 28 \times 24 \times 60 \times 0.001 = 40.32\text{ minutes}

40.32 分钟只是等价解释。request-based SLO 的实际预算仍是事件,不应把一 小时的低流量故障与一小时的流量高峰当成同一件事。

burn rate 定义为:

$$ \text{burn rate}

\frac{\text{observed bad-event ratio}} {1-\text{SLO target}} $$

对 99.9% 目标,14.4 倍 burn 对应 1.44% bad ratio。Google SRE Workbook 给出的 multiwindow 起点是:

route 长窗 短窗 burn 按本章 28 天窗口约消耗预算
page 1 h 5 min 14.4x 1 h 内 2.14%
page 6 h 30 min 6x 6 h 内 5.36%
ticket 3 d 6 h 1x 3 d 内 10.71%

这是起点,不是常数。实际规则必须按流量、后果和 notification cost 调整。 原始推导见 Google SRE Workbook 的 Alerting on SLOs

观察与告警的边界

Pigsty v4.5 提供:

VictoriaMetrics       time-series ingestion / storage / query
VictoriaLogs          structured log storage / query
VMAlert               rule evaluation
Alertmanager          grouping / inhibition / routing / notification
Grafana               dashboards and investigation entry

PostgreSQL、PgBouncer、HAProxy、Patroni 和主机事实通过 clsinsip 等身份维度关联。当前实现说明以 Pigsty Monitoring SystemPGSQL Monitoring 为准。

但平台不能凭空产生业务语义。第 25 章还需要实现:

pg36_shop_request_outcomes_total
pg36_shop_request_duration_seconds
pg36_shop_commit_visibility_probes_total
pg36_shop_reconciliation_mismatches
pg36_shop_restore_evidence_age_seconds

这些名称是本书的应用合同,不是 Pigsty 当前内置指标。将来改名可以,改变 eligible/good/missing 语义则必须重新评审 SLO。

四类操作文档

文档 何时使用 核心区别
SOP 可重复的日常动作 已知输入、稳定步骤、例行验证
Incident Runbook 症状已经发生 先保安全、边诊断边决策
Recovery Drill 证明恢复路径 隔离、预设验收、保留计时与结果
Change Plan 一次具体变更 精确目标、窗口、版本、批准与回退

“切主 SOP”这个名称可能掩盖两种完全不同的动作:

planned switchover
  current leader healthy
  authority and candidate known
  client gap can be measured

unplanned failover
  failure and write authority may be ambiguous
  fencing comes before promotion
  unknown outcomes must be reconciled

不能因为两者最终都出现“新主库”,就复用同一套前提和停止线。

高风险动作的基本不变量

本章把动作分成 L0–L3:

等级 典型动作 基本要求
L0 只读观察、验证证据 精确目标,不改变远端状态
L1 有界、可逆、低影响变更 预览、验证、回退
L2 权限、模式、池或运行态变更 独立批准、停止线、证据
L3 删除、恢复、拓扑和大影响动作 双人控制、延迟确认、强制门禁

L2/L3 使用不同的 requester、approver 与 executor。双人控制并不是两个人 盯着同一条未核对的命令按回车;合格的独立批准人必须检查:

  • 目标是否精确;
  • 影响半径是否可信;
  • 前置事实是否新鲜;
  • 动作是否与已批准 artifact hash 一致;
  • stop condition 是否机器可判定;
  • 回退/前滚是否真的可执行;
  • 未知结果是否会被错误重试。

NIST SP 800-53 Rev. 5.1 的变更访问限制包含 dual authorization。它是风险控制的参考,不意味着每个 组织必须机械照搬同一审批流。

正式实验

本章提供一套纯 L0 实验:

正式运行:

run id          34909737-527a-460c-927c-d9d71c93aa13
captured        2026-07-29T21:57:40.539Z
target          pg36-l2-vagrant/pg-test
mode            read-only
mutation        none
service owners  4 functions
objectives      3 ratio + 2 control
alerts          7 accepted + 1 actionless rejected
SOPs            4
evidence types  6
counterexamples 20 rejected
upstream runs   ch20–ch23, bound by run id and SHA-256
production      pending

重新执行第 19 章只读 gate 后,四台主机与四个 PostgreSQL member 仍通过 accepted-with-exceptions;六项沙箱例外没有被隐藏。

形式化验证故意尝试:

  • 宣称 production SLO;
  • 删除 platform owner;
  • 把 process alive 当服务健康;
  • 把目标改成 100%;
  • 排除计划维护;
  • 把缺失数据当健康;
  • 让 exhausted budget 没有后果;
  • 给指标加入未约束客户标识;
  • 接受没有动作的 page;
  • 让 cause/capacity 直接 page;
  • 把 backup exit 0 当恢复证明;
  • 让一人自批自执行;
  • 让 break-glass 跳过目标与证据;
  • 允许 evidence 保存 secret;
  • 伪造上游 restore run id;
  • 把一次沙箱切换称为生产证明。

二十个变体全部被拒绝。实验没有部署告警,也没有联系真实值班人。

本章目录

24.1 服务目录与责任模型

24.2 SLI、SLO 与错误预算

24.3 SOP、Runbook 与变更治理

24.4 观察与告警契约

24.5 证据、审计与合规

24.6 实战:把 pg36_shop 纳入服务治理

权威资料

这些资料提供方法和组件事实;pg36_shop 的服务语义、阈值和治理政策仍由 本章合同负责。

章末验收

不要用“文档已经发布”验收本章。应当能回答:

  • 能否从一次用户动作追到 eligible/good 事件定义?
  • 能否指出这个定义在哪个测量点实现?
  • 没有数据时,系统是 unknown、failed 还是有独立 fallback?
  • 正确性错误是否会被平均比例掩盖?
  • 计划维护是否仍反映在用户体验中?
  • 错误预算状态是否改变变更权限和速度?
  • 每个 page 是否有 owner、runbook、第一安全动作和恢复验证?
  • cause metric 是否只用于诊断,而不会重复 page?
  • capacity 是否形成有期限的 owned ticket?
  • 高风险动作能否被同一身份申请、批准和执行?
  • break-glass 是否仍绑定目标、时限、证据和轮换?
  • backup success 之外,是否有隔离恢复与应用验收?
  • evidence 是否能证明 source、target、time、collector 和 hash?
  • evidence 是否明确不保存 secret 与不必要的个人数据?
  • 能否运行反例并看到 validator 真实拒绝?
  • 是否清楚哪些结论仍然 production pending

如果这些问题没有答案,再多告警、审批单和仪表盘也只是组织噪声。


上一章:固若金汤:认证、授权与数据安全 · 返回下卷导读 · 下一章:望闻问切:监控体系与可观测诊断 · 查看全书目录 · 查看索引中心

24.1 服务目录与责任模型

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

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 服务所有者、数据所有者与平台所有者

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

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

本章采用四个稳定职能:

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

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

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 负责从用户视角定义合同:

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 负责说明“一行数据代表什么”和“错误意味着什么”:

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 恢复路径;
  • 容量、升级、安全基线;
  • 指标、日志、告警与值班执行。

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

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 是事件期间的协调角色:

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

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

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

服务卡中的:

{
  "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,但最常见 的失败是每个格子都填满:

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 都应回答三类问题:

decision
  我能批准或拒绝什么?

evidence
  我需要看见哪些事实?

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

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

24.1.2 等级、规格、依赖、值班与升级路径

服务等级是一组义务

goldsilverbronze 如果没有义务,只是颜色:

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 明确写:

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 回答:

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。依赖模型应同时表达:

logical redundancy = 3 PostgreSQL members
physical independence = not established

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

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

一条值班 route 至少固定:

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

确认时限不是修复时限:

acknowledge 5 minutes
  != resolve 5 minutes
  != RTO 5 minutes

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

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

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

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。其中一项依赖:

{
  "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。

服务目录的最小字段

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 实例健康不等于业务服务健康

六层证明

本章使用六层健康链:

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

因此:

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 约束、合成身份执行:

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:

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 章的切换演练正是按:

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 “时间延迟”会在空闲时误导

常见查询:

SELECT now() - pg_last_xact_replay_timestamp();

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

对写后读新鲜度,应使用:

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

cluster healthy 也不等于 entrypoint healthy

可能出现:

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

clsinsip 让这些事实能关联到同一个 Pigsty 对象。应用仍需要提供:

  • operation class;
  • eligible/good outcome;
  • user latency;
  • commit token;
  • domain reconciliation;
  • service/environment。

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

三个判断练习

情形 A:一台 replica down,用户指标正常

user availability       healthy
freshness               healthy
remaining redundancy    degraded

处理:

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

情形 B:三台 PostgreSQL 都绿,下单 100% 失败

database component      healthy
service availability    failing

处理:

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

情形 C:请求 99.999% 成功,但出现一条跨租户数据

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 窗口内用户结果 根因

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

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 与错误预算 · 查看全书目录 · 查看索引中心

24.2 SLI、SLO 与错误预算

“数据库必须高可用”不是 SLO。它没有说明:

  • 测什么;
  • 从哪里测;
  • 什么算好;
  • 什么进入分母;
  • 多长窗口;
  • 允许多少失败;
  • 没有数据怎么办;
  • 未达标后改变什么。

可执行的 SLO 至少是:

service + journey
  + eligible event
  + good event
  + measurement point
  + target
  + window
  + exclusions
  + missing-data semantics
  + error-budget policy

本节从用户事件开始,而不是从 PostgreSQL exporter 已经有什么指标开始。

24.2.1 可用性、延迟、正确性与数据新鲜度

先分清五个术语

术语 含义 例子
SLI 实际测量值 28 天 good/eligible = 99.93%
SLO SLI 的内部目标 28 天至少 99.9%
SLA 对外承诺与后果 未达 99.9% 触发服务补偿
error budget 目标允许的 bad events 0.1% eligible events
control objective 不适合平均掉的控制 无未解释数据差异

SLA 可能引用 SLO,但两者不是同义词。本章只设计内部服务治理合同,不起草 法律或商务条款。

ratio SLI 的基本形式

Google SRE Workbook 建议优先把 SLI 写成 good events 与 total/eligible events 的比率,因为它自然落在 0–1,并能与预算相连:

$$ \text{SLI}

\frac{\text{good events}} {\text{eligible events}} $$

详见 Implementing SLOs

关键不是公式,而是 event classification。

可用性:成功必须能核对

本章的下单可用性:

eligible
  authorized
  syntactically valid
  admitted at application boundary

good
  declared success response
  AND idempotency token resolves to exactly one committed order

事件分类:

情形 eligible good 原因
合法请求,提交并返回成功 完整成功
合法请求,应用 500 用户失败
合法请求,连接在 commit 后断开,尚未核对 否,直到核对 不把 unknown 假装成功
合法请求,返回成功但查不到订单 错误承诺
合法请求,创建两张订单 幂等性失败
语法不符合已发布 API 不适用 未被服务接纳
正确拒绝无权限身份 不适用 安全合同正常工作
应有权限却因配置错误被拒绝 服务失败,不是“安全排除”
服务接纳后客户端取消 依实际结果 不能事后从分母删除

HTTP 2xx、SQL 无异常或 transaction commit 任一单独都不够。good event 应当 表达用户获得的承诺。

不要用“成功查询数”混合所有操作

高流量健康查询会淹没低流量关键操作:

health endpoint     100,000,000 good
place-order              1,000 all failed
combined SLI                99.999%

所以 operation class 必须按后果拆分:

  • place-order
  • read-own-order
  • admin-report
  • background-reconcile

不能为得到好看的总数,把不同用户旅程放入同一个分母。

延迟:问“多少事件够快”,不要只看平均值

本章延迟目标:

eligible:
  same admitted order attempts as availability

good:
  final reconciled response completes within 250 ms

target:
  99% over rolling 28 days

相应 SLI:

$$ \text{latency SLI}

\frac{ \left|\left{e \in E: good(e) \land latency(e) \le 250\text{ ms} \right}\right| }{ |E| } $$

其中 EE 是所有 eligible events;快速失败虽然耗时很短,也不进入分子。

快速失败不应自动成为 latency good。若一个请求 5 ms 返回 500,它在用户旅程 上既不可用,也没有完成目标动作。

平均延迟会掩盖尾部:

99 requests * 10 ms
1 request    * 10 s
average        109.9 ms

平均值看似低于 250 ms,但最慢用户等了十秒。比例 SLO 或分位数更能表达尾部。 用于预算时,固定阈值的 good/eligible 比例比“p99 的月平均”更容易严格累计。

histogram 边界必须在采集前确定

若 histogram 没有 0.25 秒 bucket,事后无法从聚合数据精确回答“多少请求 低于 250 ms”。因此 SLO 先于 instrumentation:

SLO threshold 250 ms
  -> histogram bucket includes 0.25
  -> counter labels fixed
  -> recording rule fixed
  -> alert query fixed

这正是本章先写观察契约、下一章才实现指标的原因。

正确性:不要用 99.9% 原谅数据错误

某些正确性可以定义为比例,例如非关键搜索结果质量。但本章的订单不变量:

  • 一个 idempotency token 只对应一张订单;
  • tenant A 看不到 tenant B;
  • 金额和状态迁移合法;
  • acknowledged order 不丢失;

使用 control objective:

success:
  scheduled or incident reconciliation finds zero unexplained mismatch

failure:
  page
  freeze affected writes
  preserve reconciliation boundary
  investigate and repair under authority

这不是声称软件永远不会出错。它是规定“发现一条此类错误时,组织不能用剩余 错误预算继续正常发布”。

正确性 control 需要:

  • 明确覆盖哪些 invariant;
  • 固定 input boundary;
  • 记录 job/version/query hash;
  • 区分 known exception 与 unexplained mismatch;
  • 防止 repair 覆盖原始证据;
  • 定期证明 reconciliation 本身仍运行。

数据新鲜度:必须与某次 commit 关联

“replica lag 小于 5 秒”有至少三种含义:

  1. 接收 WAL 与 primary 的 byte distance;
  2. replay 记录的 timestamp 距当前 wall clock;
  3. 用户的一次已提交写入在读取路径上何时可见。

用户关心第三种。前两种用于诊断。

本章 freshness event:

eligible
  probe has a known committed token
  probe uses the declared read path

good
  token becomes visible within 5 seconds

流程:

write unique token
  -> reconcile commit
      -> poll replica/read endpoint
          -> visible_at - committed_at

这样能覆盖:

  • WAL 传输和 replay;
  • HAProxy route;
  • PgBouncer;
  • query path;
  • cache;
  • 应用序列化。

now() - pg_last_xact_replay_timestamp() 在空闲时会增长,不能替代这个 probe。

恢复就绪不是可用性 SLO

服务连续运行 28 天,不代表能从灾难恢复。恢复 control:

success:
  isolated restore drill passed within 90 days
  AND after material recovery-path changes

verification:
  backup identity
  required WAL
  target marker
  application invariants
  isolated listener
  stopped postmaster

backup completed 只能作为输入证据。第 21 章已经证明:

backup
  -> WAL coverage
      -> restore
          -> start in recovery
              -> reach target
                  -> promote in isolation
                      -> verify data
                          -> stop

缺任一阶段,不能把 recovery readiness 标成 good。

四类目标的组合

目标 用户问题 主要测量 失败后果
availability 能否完成动作 outcome counter + reconciliation consume budget/page
latency 是否足够快 edge histogram consume budget/page
freshness 已提交状态何时可见 commit-token probe consume budget/route change
correctness 数据是否可信 invariant reconciliation freeze/page
restore readiness 能否恢复 isolated drill evidence age block risky change

一个绿色目标不能抵消另一个红色 control。

24.2.2 测量点、统计窗口与排除条件

测量点越靠近用户,覆盖越完整

一条请求的观测点:

client
  -> CDN / gateway
      -> application
          -> HAProxy
              -> PgBouncer
                  -> PostgreSQL
测量点 能覆盖 看不到
PostgreSQL SQL、transaction、lock、I/O gateway/app/network errors
PgBouncer pool wait、server assignment app correctness
application user operation、business outcome client edge/network
gateway user-visible HTTP result commit correctness unless correlated
client/synthetic end-to-end experience 所有真实用户分布

通常使用:

primary SLI          application edge or gateway
correctness join     commit/outcome reconciliation
fallback             independent synthetic path
diagnosis            Pigsty + PostgreSQL component telemetry

若应用不能立刻增加指标,可以暂用 synthetic probe,但要记录 coverage gap。不要 因为 PostgreSQL 指标更容易获得,就悄悄把 SLO 测量点向内移动。

事件记录与时序指标各有用途

原始事件适合核对:

event_time
operation_class
eligible
outcome_class
latency
release
synthetic/real
correlation_token_hash

时序 counter/histogram 适合在线聚合与告警。不要把每个 order id 放进 metric label;需要 drill-down 时,用受控 event/log store 通过 correlation id 连接。

一个教学用关系表:

CREATE TABLE slo_event (
    observed_at      timestamptz NOT NULL,
    service_id       text        NOT NULL,
    operation_class  text        NOT NULL,
    eligible         boolean     NOT NULL,
    good             boolean,
    latency_ms       integer,
    outcome_class    text        NOT NULL,
    release_id       text        NOT NULL,
    synthetic        boolean     NOT NULL
);

生产上不一定用 PostgreSQL 保存所有事件;这里用 SQL 展示语义。

过去 28 天:

WITH eligible AS (
    SELECT *
    FROM slo_event
    WHERE service_id = 'pg36_shop'
      AND operation_class = 'place-order'
      AND observed_at >= clock_timestamp() - interval '28 days'
      AND eligible
)
SELECT
    count(*)                                              AS eligible,
    count(*) FILTER (WHERE good)                          AS good,
    count(*) FILTER (WHERE NOT coalesce(good, false))     AS bad,
    count(*) FILTER (WHERE good)::numeric
      / NULLIF(count(*), 0)                               AS sli
FROM eligible;

good IS NULL 若表示 unknown,预算查询应暂按 bad 或单独阻断,而不能由 count(*) FILTER (WHERE good) 自动消失后仍声称完整。

延迟:

SELECT
    count(*) FILTER (
        WHERE good
          AND latency_ms <= 250
    )::numeric / NULLIF(count(*), 0) AS latency_sli
FROM slo_event
WHERE service_id = 'pg36_shop'
  AND operation_class = 'place-order'
  AND observed_at >= clock_timestamp() - interval '28 days'
  AND eligible;

这里要求 availability good 且足够快,避免快速错误通过延迟目标。

滚动窗口与日历报告是两件事

本章采用滚动 28 天:

at every evaluation:
  [now - 28 days, now]

优点:

  • 每天都代表同样长度;
  • 没有月初“预算重置”错觉;
  • 适合持续 alert/budget decision。

28 天是四周,不是自然月。财务、客户或合规可能要求 calendar month/quarter 报告,应单独生成,不要把两个窗口混成一个数字。

推荐节奏:

continuous     recording and alerts
weekly         service/error-budget summary
quarterly      objective and policy review
event-driven   review after major incident or architecture change

Google SRE 的 SLO 实施章节也讨论四周滚动窗口、周汇总和季度报告。这些是实践 起点,不是对所有组织的强制周期。

长窗口防止遗忘,短窗口缩短响应

只有月窗:

  • 稳定但反应慢;
  • 一次快速事故可能在总体比例中暂时不显眼。

只有 5 分钟窗:

  • 反应快;
  • 流量低时一个错误就剧烈波动;
  • 容易被短暂 blip 打扰。

multiwindow 同时要求长窗和短窗超过同一 burn threshold:

long window   proves material budget consumption
short window  proves condition is still active

这比简单 error_rate > 1% for 5m 更贴近 SLO 后果。

排除条件应在事件发生前定义

本章默认排除:

  • 在 admission 前被正确拒绝的语法错误;
  • 正确拒绝的越权身份;
  • 服务接纳工作前的客户端取消。

默认不排除:

  • 计划维护;
  • 发布导致的错误;
  • 内部依赖故障;
  • operator mistake;
  • 容量不足;
  • “已知问题”;
  • 未核对的 unknown result。

原因很简单:用户并不会因为中断有 change ticket 就得到服务。

若产品真的有公开维护窗口,应在服务合同中定义独立承诺,而不是事故后从事件 表删除数据。

例外必须不可追溯篡改

一个 SLO exception 至少记录:

exception_id
approved_before_event
exact start/end
affected operation classes
reason
independent approver
event count before exclusion
event count after exclusion
evidence manifest

禁止:

SLO missed
  -> label incident "maintenance"
      -> recompute report
          -> target met

这种做法破坏了 SLO 作为决策工具的价值。

telemetry 缺失不能等于零错误

PromQL 查询经常在 series 消失时返回空向量。若 dashboard 把空值渲染为 0, 会出现:

exporter down
ingestion broken
rule evaluation broken
dashboard shows 0 errors

每个 SLI 必须定义:

  • expected series cadence;
  • no-traffic 与 missing telemetry 如何区分;
  • absent() 或 freshness check;
  • 独立 synthetic fallback;
  • metamonitoring route。

本章统一原则:

missing = unknown-and-monitored
missing != healthy

低流量服务不能照搬高流量阈值

若 5 分钟只有两个请求,一个失败就是 50% error rate。可以:

  1. 运行 end-to-end synthetic probe;
  2. 延长窗口;
  3. 合并后果相同的 operation,但不能用无关流量稀释;
  4. 使用明确评审的 time-based 或 user-minute SLI;
  5. 对关键 batch 用“最近两次应完成周期”定义 freshness;
  6. 将零容忍 correctness control 独立出来。

降低告警灵敏度不能变成不观察低流量关键路径。

标签维度既要可诊断,也要有界

建议:

service
operation_class
environment
region / cell
outcome_class
synthetic

谨慎:

release_id     有界保留或 exemplar
sql_fingerprint
error_class

禁止无界:

customer_id
tenant_id
order_id
raw_sql
error_message

Pigsty 组件关联使用 clsinsip;应用 SLI 使用 service 维度。不要把 实例 identity 当成服务 identity。

24.2.3 错误预算如何约束变更速度

预算不是允许主动制造错误

目标 99.9% 不表示可以计划使用 0.1% 伤害用户。预算用于在可靠性与变化之间 做有数据的选择:

budget healthy
  -> can take reviewed change risk

budget burns fast
  -> stop adding correlated risk
  -> investigate user-impacting failures

budget exhausted
  -> prioritize reliability
  -> only emergency/security/legal/direct-repair exceptions

100% 通常不是合适的 ratio target:

  • 它没有 error budget;
  • 任一测量噪声都成为违约;
  • 团队会隐藏错误或停止变化;
  • 它仍不能保证 correctness 和 recovery。

“永不丢数据”这样的要求应拆成 durability/control design,而不是伪装成 100% request ratio。

事件预算

若目标 $T$,eligible event 数 $N$:

Bevents=N(1T) B_\text{events}=N(1-T)

本章:

N = 10,000,000
T = 0.999
B = 10,000 bad events

观察到 7,500 bad:

budget consumed=750010000=75% \text{budget consumed} = \frac{7500}{10000} = 75\%

剩余 25%,进入 constrained 边界。

等价时间预算

时间可用性解释:

Btime=W(1T) B_\text{time}=W(1-T)

28 天、99.9%:

window          2,419,200 seconds
budget          2,419.2 seconds
                40.32 minutes

不要把它用于篡改 event-based 结果。高峰五分钟可能消耗的用户失败数远大于 低谷四十分钟。

burn rate

$$ \text{burn}

\frac{\text{observed bad ratio}} {1-T} $$

99.9% 的 allowed bad ratio 是 0.001:

observed bad burn
0.05% 0.5x
0.10% 1x
0.60% 6x
1.44% 14.4x
10% 100x

1x 持续整个窗口恰好用完预算;14.4x 若持续,会非常快地耗尽。

为什么 fast burn 使用 AND

可用性 page:

bad_ratio_1h > 14.4 * 0.001
AND
bad_ratio_5m > 14.4 * 0.001

长窗证明这不是一个无关紧要的点;短窗证明问题仍在。若使用 OR:

  • 长窗已受历史事故影响但当前恢复,仍持续 page;
  • 短窗单个噪声也会 page。

具体查询和 for 将在第 25 章实现。

四状态政策

本章 policy:

healthy:剩余大于 50%

  • 正常评审发布;
  • 仍要投资预防性可靠性;
  • 不能因为预算充足跳过变更控制。

watch:25%–50%

  • 减少并发 L2/L3;
  • 每周分析最大预算消费者;
  • 提前安排修复。

constrained:0%–25%

  • 暂停非必要高风险功能;
  • material change 需 service + platform 共同例外;
  • 可靠性修复优先;
  • 加强观察窗口。

exhausted:小于或等于 0

  • 冻结非紧急高风险变更;
  • 打开 incident 或 reliability review;
  • 只允许直接恢复可靠性、安全、法律或紧急动作;
  • 恢复节奏需要记录共同决定。

policy 必须说明哪些变化仍能进行

“冻结所有变更”可能阻止修复。应按意图和风险判断:

变化 exhausted 时
新推荐功能 通常冻结
无关 UI 文案 可按低风险政策评审
修复当前错误原因 允许,但仍需安全门禁
安全凭据紧急轮换 允许,不能跳过证据
扩容避免迫近 outage 允许,需验证与回退
重新定义 eligible 排除错误 禁止
降低 SLO 让报表变绿 先与 stakeholder 重新谈判,不能追溯

错误预算影响速度,不取消风险管理。

预算的所有者

service owner
  owns user priority and release trade-off

platform/reliability owner
  owns measurement integrity and technical risk

data/security owner
  can impose non-budget controls for correctness/privacy

不能由开发团队单方面改分母,也不能由 DBA 单方面降低服务目标。

防止四种 gaming

事后改变 eligibility

错误发生后把它归类为“不算用户请求”。

把事故改名 maintenance

变更单不能让用户中断消失。

把测量点移到内部

gateway 错误很多时,改看 SELECT 1

用大流量稀释关键路径

把失败的下单和成功的健康检查合并。

防线:

  • versioned SLO policy;
  • event classifier tests;
  • before/after counts;
  • independent approval;
  • immutable exception;
  • change log;
  • 定期抽样原始结果。

从 SLO 到变更门禁

变更请求要读取当前预算:

change risk       L2 schema release
budget state      constrained
purpose           new feature
decision          defer

change risk       L2 schema release
budget state      constrained
purpose           remove source of current failures
decision          allow with joint approval, narrow blast radius,
                  canary, stop line and rollback

门禁输入必须固定时间:

budget snapshot at approval
budget snapshot immediately before execution

审批后若 fast-burn page 触发,执行器应自动停止,而不是拿旧截图继续。

本章 SLO 文件

完整机器可读政策:

重点不是 JSON 语法,而是同一 objective id 在三份文件中保持一致:

SLO-AVAILABILITY
  definition in slo-policy
  source/query/missing in observation-contract
  burn alerts in alert-candidates

validator 会拒绝 missing objective、100% target、planned-maintenance exclusion、 missing=healthy 和 exhausted-with-no-action。

SLO 评审清单

  • journey 与 operation class 是否明确?
  • eligible 是否能由代码或查询判定?
  • good 是否包含用户真正获得的结果?
  • unknown outcome 如何核对?
  • 快速失败会不会错误通过 latency?
  • correctness 是否被独立 control 保护?
  • freshness 是否与 commit token 关联?
  • restore readiness 是否来自恢复而非 backup job?
  • 测量点是否尽可能靠近用户?
  • rolling window 与 calendar report 是否分开?
  • exclusion 是否事先定义、不可追溯?
  • missing telemetry 是否变成 unknown?
  • 低流量是否有 synthetic 或其他明确策略?
  • event budget 算术是否有自动测试?
  • budget state 是否改变变更行为?
  • 是否禁止用改分母、改测量点和混流量 gaming?
  • 目标是否由真实 stakeholder 批准,还是仍为教学输入?

当这些问题有答案时,SLO 才能驱动告警和变更。下一节把这些决定放进可执行 操作流程。


上一节:服务目录与责任模型 · 返回本章目录 · 下一节:SOP、Runbook 与变更治理 · 查看全书目录 · 查看索引中心

24.3 SOP、Runbook 与变更治理

一份只有命令的文档,会让操作更快,却不一定更安全:

ssh node
run command
wait
run next command

它没有回答:

  • 这是哪种故障或变更?
  • 当前事实符合文档前提吗?
  • 操作者有权改变哪个目标?
  • 影响哪些用户和数据?
  • 哪一步必须停?
  • 命令超时后是失败、成功还是未知?
  • 如何证明结果,而不是只看 exit code?
  • 应当 rollback 还是 roll forward?
  • 哪些证据可以保留,哪些 secret 不能导出?

可执行文档必须同时是动作合同、停止合同和证据合同。

24.3.1 日常操作、故障处置与恢复演练

四类文档解决不同不确定性

文档 环境 不确定性 主要形态
SOP 日常、重复 输入和结果基本已知 线性步骤
Incident Runbook 已有症状 原因和状态未知 分支与决策点
Recovery Drill 受控演练 恢复路径能否工作未知 假设、计时、验收
Change Plan 一次变更 版本、目标、窗口特定 精确 artifact 与批准

同一个动作可以出现在不同文档中,但语境不同。

例如 credential rotation:

routine SOP
  planned age-based dual-version rotation

incident runbook
  suspected disclosure; old identity cannot be re-enabled

drill
  prove clients, pool and revoke path can rotate without secret export

change plan
  rotate pg36_shop production identity at a named time and target

不能把 routine SOP 原样用于凭据泄露事故,因为 rollback 到旧 secret 在事故中 不是安全选择。

SOP:稳定输入下的可重复流程

一份合格 SOP 至少包含:

identity
  id / owner / version / review date

scope
  exact supported environments and versions

authority
  who may run / who approves / what role is used

trigger
  scheduled or event condition

preconditions
  facts that must be true

procedure
  bounded, idempotent phases

stop conditions
  facts that prohibit continuing

verification
  technical and service postconditions

rollback-or-roll-forward
  decision boundary and procedure

evidence
  what to capture, redact, hash, retain

SOP 中最重要的句子往往是:

STOP if ...

而不是最长的命令。

Runbook:从症状出发,而不是假设根因

告警是:

PG36ShopAvailabilityFastBurn

Runbook 不应开头就执行 failover。合理顺序:

1 establish user impact and current window
2 freeze concurrent risky changes
3 reconcile unknown order outcomes
4 check recent release/config/access/topology events
5 inspect application -> entry -> pool -> PostgreSQL
6 choose the smallest safe intervention
7 verify user SLI and data control
8 preserve decision and evidence

若先看到 replica lag 就 promote,可能:

  • 切错根因;
  • 扩大 write gap;
  • 引入 split-brain 风险;
  • 让原本健康的 write path 中断;
  • 丢失调查证据。

Runbook 应提供 decision tree:

user availability burns?
  no  -> diagnose component, ticket/capacity path
  yes -> correctness at risk?
           yes -> freeze affected writes, data/security path
           no  -> entry path failing?
                    yes -> route/pool diagnosis
                    no  -> transaction/lock/load diagnosis

它不能穷举所有根因,但必须约束高风险捷径。

Recovery Drill:证明“能恢复”

backup SOP 和 restore drill 不同:

backup SOP
  run backup
  repository receives objects
  check reports healthy

restore drill
  select a recovery objective
  prove backup + required WAL coverage
  restore into isolation
  start in recovery
  reach target
  promote only inside isolation
  verify application invariants
  stop recovered postmaster

演练要在开始前固定 success criteria。若恢复后才决定“只要能连上就算成功”, 结果会向已有事实偏移。

本章的 SOP-BACKUP-RESTORE 要求:

  • source cluster 和 repository 明确;
  • recovery target 固定;
  • destination 无 production listener;
  • 验证 query 与预期先冻结;
  • WAL 缺失、identity 模糊或 isolation 失败立即停止;
  • 时间只记为沙箱 observation,不称 production RTO。

Drill 不应依赖临场英雄主义

有效演练测试:

  • 文档是否完整;
  • 权限是否可用;
  • secret 是否能安全获得;
  • 工具版本是否兼容;
  • 依赖是否存在;
  • 验收是否机器可判;
  • 证据是否完整;
  • 交接是否能继续。

只让原作者凭记忆完成,证明的是个人能力,不是组织恢复能力。可在后续轮次让 另一位合格 operator 按文档执行,原作者只观察和记录歧义。

Change Plan:把抽象 SOP 绑定到一次动作

SOP 可以写:

planned switchover procedure

Change Plan 必须写:

change_id            CHG-...
service              pg36_shop
environment          production
cluster              exact cluster identity
current leader       fact captured at T0
candidate            exact member
artifact hash        reviewed command/config version
window               exact start/end
requester            durable identity
approver             different durable identity
executor             different durable identity
stop lines           current values and thresholds
rollback/forward     decision
observation          SLI + probe + component facts

“按切换 SOP 执行”不能替代这些单次事实。

版本和环境必须受支持

文档应写:

tested:
  Pigsty v4.5.0
  PostgreSQL 18.6
  Patroni 4.1.3
  PgBouncer 1.25.2

not implied:
  all earlier/later versions
  cloud-managed PostgreSQL
  a different DCS
  session pool mode

命令、输出字段和行为会随版本改变。升级计划必须同时检查 SOP 与 validator。

日常、故障和演练的证据粒度不同

类型 关键证据
routine target、输入版本、执行结果、postcondition
incident timeline、user impact、decision、unknown outcome
drill hypothesis、predefined success、timing、exception
change request/approve/execute identity、artifact hash、closeout

不能因为 incident 需要更多证据,就把所有 routine 日志永久保存;也不能因 routine 简洁,在安全事故中只留一个 shell exit code。

24.3.2 申请、评审、执行、验证与回退

完整生命周期

本章使用十一阶段:

request
  -> classify risk and authority
      -> freeze preconditions and stop lines
          -> preview and test
              -> approve
                  -> execute
                      -> observe
                          -> verify
                              -> rollback or roll forward
                                  -> retain evidence
                                      -> close and review

“批准”不是生命周期的起点,也不是结束。

1. Request:先说明目的,不要从命令开始

请求至少回答:

why
  user/business/reliability/security need

what
  desired state, not just command

where
  exact service/environment/cluster/object

when
  proposed window and dependency

impact
  users/data/capacity/recovery/security

success
  measurable postconditions

反例:

please run ALTER TABLE tonight

合格:

expand pg36_shop order schema with nullable external_ref;
old and new app versions remain compatible;
no table rewrite; lock wait stops after 2 s;
roll back by disabling new writer before semantic cutover.

2. Classify:按实际影响,不按团队习惯

本章风险级别:

L0:只读

  • 查询统计视图;
  • 渲染配置 diff;
  • 验证已有 evidence;
  • 不改变数据库、pool、topology、credential 或 route。

L0 仍要绑定目标,避免拿错误环境的证据做结论。

L1:有界、可逆、低影响

  • 增加 dashboard;
  • 发布 disabled alert;
  • sandbox 中增加窄权限 NOLOGIN role。

需要 preview、verification 和 rollback。

L2:material state

  • credential rotation;
  • online schema expansion;
  • pool limit;
  • role/permission;
  • 生产参数 reload;
  • 业务路由变化。

需要独立批准、停止线、完整证据。

L3:destructive/topology/recovery

  • failover;
  • restore/promotion;
  • data deletion;
  • cluster reset;
  • 大范围不可逆迁移。

需要最强 target binding、双人控制、延迟确认或等价门禁。

风险由 blast radius、可逆性、未知结果与数据后果共同决定,不是“SQL 只有 一行所以低风险”。

3. Freeze preconditions:审批的是事实快照

preflight 可以包括:

target identity
current leader/timeline
replica state
backup and WAL coverage
disk and capacity headroom
blocking sessions
error-budget state
current release
config hash
credential and authority

每个事实要有:

  • source;
  • captured_at;
  • freshness limit;
  • expected value;
  • stop behavior。

批准后到执行前可能发生变化。执行器必须重新采集关键事实:

approved leader = pg-a
current leader  = pg-b
=> stop, do not reinterpret the plan

4. Preview and test:使用相同 artifact

好的 pipeline:

render artifact
  -> hash
      -> test same hash
          -> approve same hash
              -> execute same hash

不要测试一套手写 SQL、执行另一套临时编辑内容。

preview 可能包括:

  • Pigsty inventory projection 和 diff;
  • SQL parse/plan;
  • lock/rewrite analysis;
  • restore dry-run facts;
  • target list;
  • expected state transition;
  • synthetic data replay;
  • rollback simulation。

preview 不能证明生产一定成功,但能拒绝明显不符合合同的输入。

5. Approve:批准目标、动作、窗口,不是空白授权

批准记录要绑定:

change id
artifact hash
exact target
risk class
blast radius
start/end
preconditions
stop lines
operator identity

“同意处理”或聊天表情不能作为 L3 授权。

6. Execute:每一步都应可观察

执行器:

  • 使用个人 durable identity;
  • 不共享 root/DBA 密码;
  • 不把 secret 放在 shell 参数;
  • 记录 phase start/end;
  • 每一阶段检查 stop condition;
  • 不并行执行计划外动作;
  • 超时后先分类结果。

脚本的 set -e 只能在命令非零时停止。它不能判断:

  • command 返回 0 但目标错误;
  • client timeout 后 server 已完成;
  • failover command 成功但路由未刷新;
  • ALTER TABLE 成功但 application 不兼容;
  • backup 成功但不可恢复。

7. Observe:动作期间看用户与组件

观察至少两条线:

service
  availability / latency / freshness / correctness probe

component
  topology / connections / locks / WAL / pool / host

只看执行命令输出,会错过用户影响;只看 SLI,又无法及时识别安全停止线。

8. Verify:状态正确,而不是命令完成

postcondition 应与 request success 一一对应。

planned switchover:

one leader
expected timeline progression
streaming replicas
client entry routes current leader
acknowledged writes present
unknown outcomes reconciled
no duplicate token

schema release:

old app works
new app works
constraint/reconciliation passes
lock/WAL within bounds
feature flag state correct

credential rotation:

new authentication works
old new-session authentication fails
existing sessions explicitly handled
pool declarations converge
secret absent from evidence

9. Rollback or roll forward:不是所有状态都能倒回

rollback 适合:

  • application feature flag;
  • additive configuration;
  • reversible grant;
  • 在 semantic cutover 前保留旧结构。

roll forward 更适合:

  • timeline 已推进的 failover;
  • 已泄露 credential;
  • 已有新版本写入的数据格式;
  • 外部系统已消费的事件;
  • commit outcome unknown。

PostgreSQL DDL 的边界

许多 PostgreSQL DDL 可以在 transaction 中回滚,但不能据此声称发布总可逆:

  • CREATE DATABASE 等命令不能在 transaction block 内运行;
  • CREATE INDEX CONCURRENTLY 不能在 transaction block 内运行,失败还可能 留下 INVALID index;
  • data backfill 已被新应用读取后,SQL rollback 不能撤回外部影响;
  • table rewrite/WAL/replica lag 已经发生;
  • application 与 schema 的兼容窗口可能关闭。

具体行为见 PostgreSQL CREATE INDEX 和第 11 章的安全模式变更。

回退计划要说明“在哪个决策点之前可回退”,而不是只写 ROLLBACK

10. Retain evidence:保存证明,不保存秘密

每次变化至少形成 manifest:

source facts and hashes
target identity
request/approve/execute identities
phase timestamps
artifact hash
observations
verification
rollback-or-forward decision
exception
final decision

不要保存:

  • cleartext password;
  • SCRAM verifier;
  • private key;
  • 完整 credential URI;
  • 无边界 bind values;
  • 不必要的客户行。

11. Close and review:关闭是一个结论

关单条件:

  • service postcondition 通过;
  • component postcondition 通过;
  • unknown outcomes 为零或有明确 owner/deadline;
  • 临时权限、route、pool/config 已恢复或正式纳管;
  • evidence 完整;
  • follow-up 有 owner 与 due date;
  • budget/incident 状态更新。

“维护窗口结束”不能自动关闭失败的变更。

一个 change record

本章要求字段:

{
  "change_id": "CHG-...",
  "service_id": "pg36_shop",
  "risk_class": "L2",
  "exact_target": "environment/cluster/object",
  "blast_radius": "declared users and data",
  "requester_identity": "workforce://...",
  "approver_identity": "workforce://...",
  "executor_identity": "workforce://...",
  "planned_start_and_end": ["...", "..."],
  "preconditions": ["..."],
  "stop_conditions": ["..."],
  "execution_hash": "sha256:...",
  "verification": ["..."],
  "rollback_or_roll_forward": "...",
  "decision": "accepted|rejected|accepted-with-exceptions",
  "evidence_manifest": "evidence://..."
}

真实系统还要验证三个 identity 不同且当时有效。

24.3.3 高风险动作的双人或延迟确认

双人控制解决什么

高风险动作容易同时发生三类错误:

intent error
  本来不应做

target error
  对错环境/集群/对象做

execution error
  步骤、参数或时机错误

独立批准人应使用独立证据检查意图与目标;执行器负责按批准 artifact 操作。 两人同时复制同一个错误命令,不构成有效独立控制。

NIST SP 800-53 Rev. 5.1 在 access restrictions for change 中描述 dual authorization:两名合格个人批准 和实施选定高风险变化,并对变化负责。组织还可轮换职责,降低串通风险。

本章将它转化为:

requester_may_approve     false
approver_may_execute      false
shared_accounts           false
qualified approver        true
target + blast radius     required
machine preconditions     required

独立不只意味着用户名不同

无效:

  • 同一人使用两个账号;
  • 两人共享一个 password;
  • approver 没有理解 PostgreSQL/Pigsty 风险;
  • approver 只看 requester 的截图;
  • 执行 artifact 在批准后被修改;
  • 两人都受同一个未验证假设影响。

有效独立性包括:

  • durable workforce identity;
  • 不同职责;
  • 足够专业能力;
  • 能访问 source-of-truth;
  • artifact hash;
  • 能拒绝;
  • 拒绝不会被绕过;
  • 事后记录。

延迟确认解决冲动和误目标

destructive action 可要求 cooldown:

request generated
  exact target + action + impact + hash
      -> independent review
          -> wait bounded interval
              -> refresh target/preconditions
                  -> typed confirmation bound to same target/hash/window

延迟让:

  • 执行者有时间发现环境错误;
  • 依赖 team 有时间反馈;
  • 自动备份/导出有时间完成;
  • 用户影响窗口再次确认。

它不适用于所有 incident。真正紧急时可以缩短 normal wait,但不能跳过 target、 authority、stop line 和 evidence。

confirmation token 必须绑定语义

差:

CONFIRM=YES

较好:

target       prod/eu-west/pg-shop
action       promote reviewed recovery target
artifact     sha256:...
window       2026-...
confirmation RECOVER_PG_SHOP_TO_...

脚本还应从运行时重新解析 target identity,而不是完全信任环境变量字符串。

break-glass 不是无规则

break-glass 允许:

  • 跳过正常排期;
  • 获取时限高权身份;
  • 执行恢复服务所需的最小动作。

它不允许:

  • 不记录目标;
  • 无止境保留高权;
  • 关闭审计;
  • 将 credential 发进聊天;
  • 跳过事后 review;
  • 使用后不轮换;
  • 将未知结果当成功。

本章合同:

may_skip_normal_wait          true
may_skip_target_and_evidence  false
time_bounded_identity         true
independent_after_review      true
credential_rotation_after_use true

emergency change 仍要选择最小动作

事故中的压力会放大 scope:

one blocked query
  -> restart all databases

Runbook 应给出干预阶梯:

observe
  -> cancel one query
      -> terminate one session
          -> isolate one application path
              -> controlled role change
                  -> restart component
                      -> recover/fail over

每一级需要新的证据和 authority。不能因为已经进入 SEV-1,就自动获得所有 destructive action 的授权。

自动化能做什么

适合机器执行:

  • 解析 inventory;
  • 验证 mode/owner;
  • 比较 target identity;
  • 采集 leader/timeline/LSN;
  • 计算 lock、WAL、budget;
  • 检查 artifact hash;
  • 拒绝非空 evidence directory;
  • 执行 bounded probe;
  • 核对 postcondition;
  • 生成 manifest;
  • 扫描 secret material。

仍需组织决定:

  • 用户损失是否可接受;
  • recovery point 是否符合业务;
  • 是否暂停业务;
  • 谁拥有 change authority;
  • 合规/隐私边界;
  • 何时允许例外。

自动化可以证明 guard 成立,不能发明授权。

guard 不是一串容易伪造的环境变量

高风险 runner 应结合:

declared target token
live target identity
inventory allowlist
environment classification
production traffic flag
data classification
approval record
artifact hash
private input mode
empty output directory

只检查 NONPRODUCTION=true 不够;操作者可以错误设置。第 19–23 章的实验 runner 还会核对 host、cluster、topology 和 fixture identity。

超时与 unknown outcome

高风险 API/SQL 超时后:

do not retry yet
  -> inspect durable state
      -> classify:
           completed
           not started
           partially applied
           unknown
      -> choose idempotent continuation or repair

例子:

  • COMMIT timeout:查 idempotency token;
  • switchover timeout:查 Patroni leader/timeline;
  • revoke timeout:从新 session 测认证与 role attributes;
  • DDL timeout:查 catalog/lock/index validity;
  • backup timeout:查 repository manifest,不凭 client exit 猜。

重试本身是一项 change,需要可幂等证明。

高风险 review 的三次停止机会

before approval
  design or authority is wrong

immediately before execution
  facts or target changed

during execution
  stop threshold or unknown outcome reached

批准人不能提前放弃后两次停止。机器 guard 也不能因为 approval 存在而忽略 live drift。

本章的 SOP 与变更政策

四个 SOP 覆盖:

backup and restore
planned switchover and failover
access and credential rotation
schema release

每份都有 precondition、stop、verification、rollback/roll-forward 和 evidence。 validator 会拒绝:

  • backup exit 0 作为全部恢复验证;
  • L2/L3 同人自批自执行;
  • break-glass 跳过 target/evidence;
  • 沙箱运行被标为 production proof。

文档演练清单

  • 文档类型是否正确,还是把 incident 写成线性 SOP?
  • scope 和版本是否明确?
  • trigger 与 authority 是否明确?
  • exact target 能否由 live evidence 验证?
  • precondition 是否带 freshness?
  • 每个高风险 phase 前是否有 stop condition?
  • 命令 timeout 后是否先核对 durable state?
  • success 是否由 postcondition 定义?
  • rollback 与 roll-forward 的决策点是否明确?
  • external side effect 是否考虑?
  • requester/approver/executor 是否真正独立?
  • break-glass 是否有时限、审查和轮换?
  • evidence 是否 secret-free?
  • 另一个合格 operator 能否只凭文档完成?
  • 文档、脚本、目标版本变化后是否重新演练?

下一节会把 SLO 与这些 Runbook 连接起来:一个告警若不能把值班人带到安全的 第一动作,就不应成为 page。


上一节:SLI、SLO 与错误预算 · 返回本章目录 · 下一节:观察与告警契约 · 查看全书目录 · 查看索引中心

24.4 观察与告警契约

监控系统不会自动知道“什么对用户重要”。它只会忠实地计算你交给它的数值。

wrong semantic + perfect query
  = precisely wrong alert

因此告警规则之前要有 observation contract:

objective
  -> source
      -> metric/event type
          -> good/total selector
              -> dimensions
                  -> query
                      -> missing semantics
                          -> fallback
                              -> owner/action

本节产出第 25 章的实现合同,而不提前声称指标和 page 已经部署。

24.4.1 每个 SLI 的数据源、查询、维度和缺失语义

一个 SLI 需要哪些字段

最小 observation contract:

字段 问题
objective_id 它实现哪个 SLO/control?
source 谁产生原始事实?
metric_type counter、histogram、gauge 还是 event?
good_selector 哪些事件进入分子?
total_selector 哪些事件进入分母?
dimensions 按哪些有界维度分解?
query_template 如何从窗口计算?
missing_semantics series 消失代表什么?
fallback 主 observation path 失败后看什么?
metric_status 已存在、待实现还是 deprecated?

少一个字段就可能改变结论。

本章的五个来源

objective source type 状态
availability pg36_shop_request_outcomes_total counter 应用待实现
latency pg36_shop_request_duration_seconds histogram 应用待实现
freshness pg36_shop_commit_visibility_probes_total counter synthetic probe 待实现
correctness pg36_shop_reconciliation_mismatches gauge 核对任务待实现
restore readiness pg36_shop_restore_evidence_age_seconds gauge 证据导出器待实现

这些是本书定义的应用 metric contract,不是 Pigsty 内置指标。metric_status 故意保留“to implement”,防止目录文档冒充运行事实。

availability query

原始 counter:

pg36_shop_request_outcomes_total{
  service="pg36_shop",
  operation_class="place-order",
  environment="production",
  eligible="true",
  outcome="good|bad|unknown"
}

bad ratio 模板:

1 -
sum(
  rate(pg36_shop_request_outcomes_total{
    service="pg36_shop",
    operation_class="place-order",
    eligible="true",
    outcome="good"
  }[$window])
)
/
clamp_min(
  sum(
    rate(pg36_shop_request_outcomes_total{
      service="pg36_shop",
      operation_class="place-order",
      eligible="true"
    }[$window])
  ),
  1
)

unknown 在核对前不进入 good,所以会消耗预算。后续事件若核对为 committed once,可通过事件管道作有审计的最终分类;不能在 dashboard 手工改值。

clamp_min 只防除零,不解决 telemetry missing。no traffic、counter absent 和 ingestion broken 要由独立 freshness/metamonitoring 判断。

latency query

histogram 需要 le="0.25" bucket:

1 -
sum(
  rate(pg36_shop_request_duration_seconds_bucket{
    service="pg36_shop",
    operation_class="place-order",
    eligible="true",
    availability_good="true",
    le="0.25"
  }[$window])
)
/
clamp_min(
  sum(
    rate(pg36_shop_request_duration_seconds_count{
      service="pg36_shop",
      operation_class="place-order",
      eligible="true"
    }[$window])
  ),
  1
)

这里示意把快速失败留在 denominator 而不进入 good bucket。实际 instrumentation 也可以统一用 outcome counter 与 duration histogram 通过 recording rule 对齐, 但必须有自动测试证明:

eligible count in availability
  == eligible count in latency

否则两个 SLO 使用不同分母,会产生无法解释的预算。

freshness query

每个 probe:

write token
commit reconciled
poll declared read path
within_bound = true/false
read_path = replica|primary|application

bad ratio:

1 -
sum(
  rate(pg36_shop_commit_visibility_probes_total{
    service="pg36_shop",
    eligible="true",
    within_bound="true"
  }[$window])
)
/
clamp_min(
  sum(
    rate(pg36_shop_commit_visibility_probes_total{
      service="pg36_shop",
      eligible="true"
    }[$window])
  ),
  1
)

probe absence:

unknown + probe-pipeline failure

不能用 pg_last_xact_replay_timestamp() 填补,因为它没有对应当前 commit。

correctness query

max(
  pg36_shop_reconciliation_mismatches{
    service="pg36_shop"
  }
)

需要配套:

  • last_success_timestamp
  • reconciliation input boundary;
  • rule/query version;
  • invariant;
  • result hash。

只导出 0 而不监控 job freshness,会在核对任务停摆后永远显示“零差异”。

missing semantics:

stale or absent reconciliation = control failure

restore evidence age

max(
  pg36_shop_restore_evidence_age_seconds{
    service="pg36_shop",
    recovery_class="named-pitr"
  }
)

阈值 90 天:

<= 7,776,000 seconds   control current
>  7,776,000 seconds   evidence stale
absent                 failed control, not infinite freshness

导出器必须只接受验证通过且完整性 hash 正确的 restore manifest。不能因为目录中 有一个新文件,就把 evidence age 归零。

PostgreSQL statistics 用于解释原因

PostgreSQL 18 提供:

  • pg_stat_activity:backend、状态、query/transaction 时间;
  • pg_stat_database:transaction、block、tuple 与 session 统计;
  • pg_stat_replication:sender、state、LSN 与同步状态;
  • pg_stat_wal_receiver:standby receiver;
  • pg_stat_archiver:归档成功/失败;
  • pg_stat_io:按 backend/object/context 的 I/O;
  • pg_stat_ssl:连接 TLS;
  • lock 与 progress views。

完整目录见 PostgreSQL Monitoring Database Activity

这些视图回答:

what PostgreSQL is doing

不直接回答:

whether pg36_shop users completed place-order correctly

访问统计应使用 pg_monitor 等受控预定义角色或更窄授权,而不是让 exporter 成为 superuser。预定义角色边界见 Predefined Roles

Pigsty v4.5 的实现层

当前 Pigsty 监控栈:

组件 职责
VictoriaMetrics 时序 ingestion、storage、query
VictoriaLogs 结构化日志
VMAlert 规则评估
Alertmanager 聚合、抑制、路由、通知
Grafana dashboard 与调查入口
exporter/agent 暴露数据库、主机和组件事实

Pigsty v4 已从旧的 Prometheus/Loki 存储迁到 VictoriaMetrics/VictoriaLogs; VMAlert 仍使用 PromQL-compatible 规则。不要复制旧文档后声称 v4 仍以 Prometheus server 存储时序。当前架构见 Monitoring System

PostgreSQL、PgBouncer、host、load balancer 尽量通过:

cls   cluster identity
ins   instance identity
ip    address identity

关联,详见 PGSQL Monitoring

维度:服务与组件分别建模

应用 SLI:

service
operation_class
environment
region/cell
outcome class
synthetic

组件 telemetry:

cls
ins
ip
database
user/role (bounded and privacy reviewed)

连接方式:

service catalog:
  pg36_shop -> pg-test

metric correlation:
  service="pg36_shop"
  dependency_cls="pg-test"

不要强行把 service 填成 ins,也不要把所有 database metric 复制一份 customer label。

cardinality 是可靠性和隐私问题

禁止 label:

  • customer_id
  • tenant_id
  • order_id
  • raw SQL;
  • error message;
  • stack trace。

原因:

  • series 数无界增长;
  • query 和 rule 变慢;
  • monitoring storage 自身失稳;
  • 用户数据进入广泛可见系统;
  • label 变化让 aggregation 不可靠。

使用:

  • bounded error class;
  • normalized SQL fingerprint;
  • exemplar/correlation token;
  • 受控日志查明具体事件。

missing-data truth table

traffic/probe SLI series 结论
有预期流量 存在 计算 SLI
有预期流量 缺失 observation failure
无真实流量 synthetic 存在 limited synthetic evidence
无真实流量 synthetic 缺失 unknown
exporter 存在 app SLI 缺失 component observable, service unknown
app SLI 存在 Alertmanager canary 失败 health known, notification path broken

“没有错误 series”与“error counter value is zero”不是一回事。

24.4.2 告警必须绑定用户影响、首个安全动作和所有者

page 的门槛

Prometheus 官方 alerting practices 总结为:

  • 保持简单;
  • 对症状告警;
  • 用良好 console 定位原因;
  • 避免无事可做的 page。

Alerting practices

本章把 page 定义为:

urgent
important
actionable
real
owned

缺一项就应考虑 dashboard、ticket 或删除。

一个 alert contract

每个 accepted alert 至少包含:

id
class
objective
expression
long/short window
severity and route
for
user impact
owner
route id
runbook
first safe action
verification
dashboard
missing semantics
silence policy
test
review expiry

这比:

alert: DatabaseHighCPU
expr: cpu > 80
severity: critical

多出来的内容,正是值班可行动性的来源。

user impact 要具体

差:

database is unhealthy

好:

eligible order attempts are failing or unreconciled fast enough
to spend 2% of the 28-day availability budget in one hour

差:

replica lag high

好:

commit-correlated reads on the declared replica path miss
the five-second visibility bound

first safe action 不是最终修复

fast-burn availability page:

first safe action:
  freeze latest risky release
  reconcile unknown order outcomes before retrying writes

它没有假设根因。之后 Runbook 才检查应用、入口、pool、PostgreSQL、锁和拓扑。

correctness page:

freeze affected writes
preserve reconciliation boundary before repair

不能先运行“修复 SQL”,否则会覆盖事故证据。

metamonitoring page:

establish an independent blackbox view
before changing the monitored database

监控断了时先恢复观察,不要凭空重启数据库。

owner 必须有 route

owner_function=service 仍不够。规则要带:

route_id
schedule
escalation
notification grouping
repeat interval

route 要用 canary 验证:

synthetic alert
  -> rule evaluator
      -> Alertmanager
          -> receiver
              -> acknowledgement record

不能只看 Alertmanager 进程 up。

for 防短暂抖动,但会增加检测延迟

Prometheus/VMAlert 风格规则可以使用:

for: 2m

条件必须持续 2 分钟才 firing。它适合滤除短 blip,但不是越大越好:

evaluation interval
+ query window behavior
+ for
+ notification delay
+ human acknowledgement
= practical detection time

correctness mismatch 可以 for: 0m,因为单个已确认 mismatch 的后果不同。

当前 Prometheus 规则语义还支持 keep_firing_for,用于条件短暂消失后继续 firing 一段时间;使用前要确认 VMAlert 当前版本的兼容和行为,并通过 rule test 验证。官方字段见 Alerting rules

silence 不是关闭问题

silence 必须有:

  • incident/change id;
  • owner;
  • reason;
  • exact matcher;
  • start/end;
  • replacement observation;
  • review。

禁止:

silence service=* severity=critical for 30 days

计划维护也应尽量使用 route/inhibition 和用户 SLO 的明确政策,而不是让所有 信号消失。

verification 决定何时恢复

fast-burn 恢复:

long and short windows recover
AND sampled idempotency tokens reconcile
AND no correctness control fails

只看到 alert resolved 可能是:

  • series 消失;
  • label 改变;
  • rule reload 失败;
  • traffic 归零;
  • silence;
  • query error。

Runbook 必须检查 missing semantics 和用户结果。

alert 要有生命周期

规则不是写完永存:

owner
created
last tested
last fired
false-positive review
runbook validity
expiry/review date
replacement/deprecation

orphaned route 或过期 runbook 应使 CI/治理 review 失败。

24.4.3 症状告警、原因告警与容量预测分开

症状 page

症状直接表示用户或数据后果:

  • availability fast burn;
  • latency fast burn;
  • commit-correlated freshness violation;
  • correctness mismatch;
  • imminent durability loss;
  • observation path loss 导致服务状态不可知。

它回答:

why wake a human now?

原因 telemetry

原因帮助定位:

  • CPU;
  • disk latency;
  • lock waits;
  • pool queue;
  • replica WAL distance;
  • cache hit ratio;
  • autovacuum backlog;
  • connection count;
  • Patroni member state。

同一用户症状可能有多个原因;同一原因也可能被 redundancy 吸收而没有用户 影响。若每个原因都 page:

one incident
  -> CPU page
  -> lock page
  -> pool page
  -> latency page
  -> replica page

值班收到五个 notification,却没有更多信息。

原因应进入:

  • linked dashboard;
  • diagnostic annotation;
  • bounded ticket;
  • automatic enrichment。

两个允许越过“用户症状”的例外

完整性/保密性

一条跨租户数据或 confirmed corruption 即使用户尚未报告,也需要立即处理。

迫近耐久性

例如所有可恢复副本/备份路径都失效且继续运行会导致不可恢复数据风险。此时 page 的依据是已定义的 durability control,不是随意的 component threshold。

例外仍要 owner、Runbook、action 和 verification。

容量是有期限的 ticket

容量预测:

forecast_days_to_capacity < lead_time + safety_margin

通常不是当前 user incident,因此:

  • 创建 owned ticket;
  • 带 forecast confidence;
  • 检查 demand/query mix/retention;
  • 给 due date;
  • 不在凌晨 page。

若容量已经造成 latency/availability,则症状 SLO 会 page;capacity facts 作为 诊断。

本章候选:

PG36ShopCapacityHorizon
  route ticket
  forecast horizon 14 days
  first action validate demand/headroom/retention/query mix

metamonitoring

监控系统也会失败:

  • exporter 停止;
  • agent 无法发送;
  • VictoriaMetrics ingestion/query 失败;
  • VMAlert rule evaluation error;
  • Alertmanager route 失败;
  • receiver 不可达;
  • dashboard query 误导。

有效 metamonitoring:

whitebox
  ingestion errors / rule errors / queue

blackbox
  expected series freshness
  external service probe
  notification canary end-to-end

Prometheus alerting practices 也建议用贯穿 PushGateway/Prometheus/ Alertmanager/email 的 blackbox 测试,而不是只盯每个组件进程。Pigsty v4 中要 把这个思想映射到 VictoriaMetrics/VMAlert/Alertmanager。

分类矩阵

signal class route 例子
user event bad ratio symptom page/ticket by burn availability
data invariant mismatch integrity page duplicate/cross-tenant
WAL distance cause dashboard replication diagnosis
pool queue cause dashboard latency diagnosis
CPU cause dashboard/ticket capacity/diagnosis
days-to-full capacity ticket disk/storage
notification canary overdue metamonitoring page blind operation

本章明确拒绝的 page

候选:

PostgresInstanceDownWithoutAction

被拒绝,因为:

  • 一台 instance 可因 offline maintenance 合理下线;
  • redundancy 可能仍满足服务;
  • 没有声明用户影响;
  • 没有 owner;
  • 没有 Runbook;
  • 没有第一安全动作。

替代:

retain instance state on topology dashboard
correlate endpoint/user-event symptoms
escalate through redundancy/durability policy if needed

validator 会故意把它的 decision 改成 accepted,并确认合同拒绝。

24.4.4 产出供 ch25 实现的告警规则清单

七个 accepted candidates

1. PG36ShopAvailabilityFastBurn

class       symptom
route       page / SEV-1
windows     1h + 5m
burn        14.4x
for         2m
action      freeze latest risky release;
            reconcile unknown writes before retry

对 99.9%:

14.4×(10.999)=0.0144 14.4 \times (1-0.999)=0.0144

即两个窗口 bad ratio 都高于 1.44%。

2. PG36ShopAvailabilitySlowBurn

route       page / SEV-2
windows     6h + 30m
burn        6x
for         5m
action      stop concurrent risky changes;
            segment by operation and release

阈值 0.6% bad ratio。

3. PG36ShopAvailabilityBudgetTicket

route       ticket
windows     3d + 6h
burn        1x
for         15m
action      open owned budget review

用于慢性消耗,不打扰夜间值班。

4. PG36ShopCorrectnessMismatch

class       integrity
route       page / SEV-1
condition   mismatch > 0
for         0m after confirmed reconciliation
action      freeze affected writes;
            preserve boundary

不使用 burn rate。

5. PG36ShopFreshnessFastBurn

class       symptom
route       page / SEV-2
windows     1h + 5m
burn        14.4x against 99% freshness target
action      route affected read-after-write journey to primary
            while preserving probe tokens

不要把所有 read traffic 永久移到 primary;这是有界安全动作,恢复后再评审 read policy。

6. PG36ShopCapacityHorizon

class       capacity
route       ticket
condition   reviewed forecast < 14 days
action      validate demand/headroom/retention/query mix

预测没有足够历史时,创建 evidence-quality ticket,而不是编造 forecast。

7. PG36MonitoringPathBroken

class       metamonitoring
route       page / SEV-2
condition   expected probe missing
            OR rule evaluation failure
            OR notification canary overdue
action      establish independent blackbox view first

rule skeleton

第 25 章可从下面开始,但必须使用 recording rules 和测试后的真实 label:

groups:
  - name: pg36-shop-slo
    rules:
      - alert: PG36ShopAvailabilityFastBurn
        expr: |
          (
            pg36_shop:sli_availability_bad_ratio:rate1h
              > 14.4 * (1 - 0.999)
          )
          and
          (
            pg36_shop:sli_availability_bad_ratio:rate5m
              > 14.4 * (1 - 0.999)
          )
        for: 2m
        labels:
          severity: sev1
          service: pg36_shop
          class: symptom
        annotations:
          summary: "pg36_shop availability burns fast"
          runbook: "runbook://RB-USER-SYMPTOM"
          dashboard: "dashboard://pg36-shop-slo"

这段只是规则语义骨架,不能直接部署,因为:

  • recording rules 尚未实现;
  • production environment/region labels 尚未固定;
  • route 与 receiver 仍是教学标识;
  • real owner 尚未批准;
  • rule evaluation interval 尚未纳入检测时间;
  • test fixture 尚未建立。

第 25 章必须交付什么

Instrumentation

  • counters/histograms 的真实采集;
  • eligible/good classifier tests;
  • commit token probe;
  • reconciliation freshness;
  • restore evidence exporter;
  • bounded labels。

Recording rules

  • 5m、30m、1h、6h、3d 等窗口;
  • label-preserving aggregation;
  • reset 和 counter semantics;
  • no-traffic/missing 分支;
  • rule unit tests。

Alert rules

  • 七个 accepted candidates;
  • actionless candidate 保持 rejected;
  • for 和 evaluation delay;
  • route/inhibition/silence;
  • review expiry。

Dashboards

  • SLI、target、budget remaining;
  • numerator/denominator;
  • eligible classification;
  • recent changes;
  • component cause drill-down;
  • missing telemetry state。

Delivery tests

  • synthetic rule firing;
  • label/annotation assertions;
  • Alertmanager grouping;
  • notification canary;
  • acknowledgement;
  • resolved 与 missing 的区分。

交接门槛

在实现规则前逐项确认:

  • objective id 与 SLO policy 一致;
  • metric status 从“待实现”变更有 evidence;
  • good/total selector 经过代码测试;
  • histogram 有目标 bucket;
  • unknown outcome 不会被算 good;
  • missing series 有独立规则;
  • labels 有界且不泄露数据;
  • Pigsty identity 使用 cls/ins/ip
  • component facts 不冒充 user SLI;
  • multiwindow 使用 AND;
  • page 有 owner、route、Runbook、action 和 verification;
  • capacity 进入 ticket;
  • cause 进入 dashboard;
  • correctness/durability 例外有独立 control;
  • notification path 自身被测试;
  • real pager 测试获得明确授权;
  • sandbox 规则没有被称为 production-approved。

完整输入见:

下一节处理这些指标、变更和操作留下的证据:证据必须足够证明决定,却不能把 秘密和个人数据无边界地复制到治理系统。


上一节:SOP、Runbook 与变更治理 · 返回本章目录 · 下一节:证据、审计与合规 · 查看全书目录 · 查看索引中心

24.5 证据、审计与合规

“日志里应该有”不是证据策略。

一份可用于运营决策的证据至少要回答:

what fact
from which source
about which exact target
collected by which identity/tool
at what time
under which authority
with which integrity check
used for which decision
retained where and for how long
disclosed to whom
deleted how

同时还要回答:

what must never enter this evidence

数据库 evidence 很容易包含 credential、SQL bind value、tenant data、backup 内容和人员身份。收集越多,不等于治理越好。

24.5.1 配置、变更、访问和恢复证据

日志、审计、证据和合规不是同义词

对象 主要用途 例子
operational log 调试运行行为 PostgreSQL error/log line
audit record 记录受关注主体动作 role/DDL/object access
evidence 支撑一个具体结论 target+hash+result manifest
compliance assessment 将证据映射到外部控制 control tested/effective/gap

一个系统可以日志很多但证据不足:

log:
  ALTER ROLE completed

missing:
  requester
  independent approver
  exact reviewed artifact
  previous state
  intended target
  new-session verification
  existing-session decision
  secret handling

反过来,保存一份结构化的 secret-free manifest 可能比复制整个日志目录更适合 日常 change evidence。

配置证据:声明、渲染、运行事实

Pigsty 管理的 PostgreSQL 配置有三层:

declared
  inventory / policy source

rendered
  generated config, HBA, service definition

observed
  pg_settings / pg_hba_file_rules / listener / service state

只保存 inventory 不能证明部署生效;只保存 SHOW 不能证明变更来源和评审。

配置 evidence:

service/environment/cluster
source revision and hash
sanitized inventory projection
rendered artifact hash
live target identity
observed setting + source + pending_restart
drift and accepted exception
collector/tool version
captured_at UTC

需要特别区分:

  • desired value;
  • current effective value;
  • reload-pending;
  • restart-pending;
  • session override;
  • role/database override。

PostgreSQL pg_settingssourcesourcefilepending_restart 等字段有助于 解释运行值,但路径和配置内容仍需按敏感性处理。官方视图见 pg_settings

变更证据:意图、授权、执行、结果

完整 change evidence:

intent
  request / desired state / user reason

authority
  requester / approver / executor / time / role

artifact
  exact SQL/config/script hash

preflight
  target / topology / budget / capacity / stop lines

execution
  phases / timestamps / tool outcome

observation
  SLI / component facts / unknown outcomes

verification
  postconditions

decision
  accept / reject / exception / rollback / roll-forward

命令历史不能替代这个链:

  • shell history 可编辑;
  • shared account 无法归因;
  • command line 可能泄露 secret;
  • 它不记录独立批准;
  • 它不证明 target;
  • 它不证明结果。

访问证据:身份链而不是密码

第 23 章区分:

workforce/workload identity
  -> login role
      -> effective role
          -> object permission
              -> RLS context

访问 evidence 可以保存:

  • durable identity id;
  • authentication mechanism/result;
  • login/effective role;
  • membership option;
  • target object/action;
  • approval;
  • SQLSTATE;
  • session id/correlation;
  • start/end/revoke;
  • policy version。

不能保存:

  • cleartext password;
  • SCRAM verifier;
  • private key;
  • session token;
  • credential-bearing URI;
  • raw PgBouncer userlist;
  • 无必要的 SQL parameter。

credential rotation evidence 保存 secret version id,而不是 secret value:

old version v17
new version v18
new auth pass
old new-session auth rejected
existing sessions handled by decision CHG-...
final role LOGIN=false

ordinary PostgreSQL logs 的边界

PostgreSQL logging 可以记录:

  • connection/disconnection;
  • statement duration;
  • SQLSTATE 和错误;
  • DDL/statement;
  • prefix 中的 user/database/application/session;
  • lock、checkpoint、autovacuum 等运行事件。

配置见 Error Reporting and Logging

但普通日志不是自动完整 audit:

  • 采样和阈值会省略事件;
  • statement 与 bind value 记录受参数影响;
  • superuser/host admin 可能改配置;
  • owner/role 语义需要额外关联;
  • log storage、access、integrity、retention 仍需治理;
  • 记录过多参数会泄露数据。

pgAudit 可以提供更结构化的 session/object audit,但仍需安装、preload、配置、 容量和审查。官方项目见 pgaudit/pgaudit。它也不能替代 change request、业务授权和 evidence chain。

恢复证据:从 backup 到 application

恢复 evidence 至少有:

source identity
backup identity
repository identity
target time/name/LSN
required WAL coverage
restore destination and isolation
start-in-recovery
target reached
promotion decision
system identifier relation
application marker/invariant checks
network/listener boundary
postmaster stopped
timings and their interpretation

第 21 章正式摘要:

run id       run_20260729T201040Z_961665aa
named PITR   accepted-with-exceptions
production   pending

本章只引用其 run id 与 SHA-256,不复制 private raw bundle,也不把单次沙箱 timing 升格为生产 RTO。

SLO 和告警证据

SLO decision 需要:

  • policy version;
  • numerator/denominator;
  • query/recording rule hash;
  • evaluation window;
  • exclusion records;
  • missing-data intervals;
  • budget state;
  • release/change markers;
  • alert firing/resolved;
  • route delivery 和 acknowledgement;
  • owner decision。

只有 dashboard screenshot 不够:

  • 时间范围可能隐藏;
  • query 可能后来改变;
  • series 可能 missing;
  • panel 可手工选择过滤;
  • screenshot 不能复算。

可以保留 screenshot 作为沟通附件,但 canonical evidence 应能机器重算。

incident evidence

事件证据:

first signal
declared severity
user/data impact
decision log
commands and target
topology/config changes
unknown outcome reconciliation
communications
recovery verification
follow-up

时间线要区分:

event time
observed time
recorded time

避免事后把“后来知道的根因”写成当时已知事实。

六类 evidence policy

本章定义:

类别 教学保留 分类
configuration 400 d internal
change 400 d confidential-operational
access 400 d confidential-security
recovery 400 d confidential-operational
SLO/alert 400 d internal
incident 730 d confidential-incident

这些数字只是教学政策,用于验证每类都有正数、访问和删除合同。真实保留期必须 经过法律、监管、隐私、调查和成本评审,不能照抄。

24.5.2 保留、不可抵赖与隐私边界

chain of custody

证据链:

collect
  source / target / collector / UTC / tool version

preserve
  immutable or object-locked original where required

manifest
  canonical file list + SHA-256

derive
  redacted review copy, original retained separately

access
  role-based and audited

hold/export
  authority and recipient recorded

delete
  policy-driven and audited

“文件还在”不能证明它没被改。

hash 证明什么

SHA-256 可以证明:

current bytes match the bytes whose digest was recorded

不能单独证明:

  • 谁采集;
  • source 真实;
  • 采集前没有被篡改;
  • timestamp 可信;
  • manifest 没被一起替换;
  • 结论正确。

增强方式:

  • signed manifest;
  • append-only/object lock store;
  • independent timestamp;
  • durable collector identity;
  • separation of duties;
  • multiple source correlation;
  • access and deletion audit。

不要把“有 hash”写成绝对不可抵赖。

不可抵赖依赖个人身份

shared postgresroot account 最多证明“某个拥有共享 credential 的人”。 要提高 accountability:

person authenticates with durable workforce identity
  -> obtains time-bounded privileged session
      -> executes under unique session/change id
          -> command and target recorded
              -> approval and result linked

仍要考虑:

  • 身份被盗;
  • host compromise;
  • log admin;
  • 时钟;
  • collusion;
  • privacy。

所以工程上更准确的说法是“提高归因与篡改检测能力”,而不是宣称数学意义上的 绝对不可抵赖。

UTC、时钟与顺序

多节点 PostgreSQL/Pigsty 证据需要:

  • UTC timestamp;
  • NTP status;
  • monotonic duration;
  • event sequence/correlation;
  • clock uncertainty。

wall clock 可调整,耗时应使用 monotonic clock。跨系统排序若只靠毫秒 timestamp 可能出错,应用事件应有 correlation/idempotency token。

最小必要收集

问四次:

purpose
  这个字段证明哪项控制?

scope
  能否用 aggregate/fingerprint 替代原值?

access
  谁真正需要?

retention
  目的结束后何时删除?

例子:

原始材料 更安全替代
password secret version id
certificate private key public certificate fingerprint
full SQL values normalized fingerprint + SQLSTATE
customer row count + synthetic marker
full connection URI host/db/user + redacted auth
raw system identifier everywhere relation matches/differs,必要时私密保存

evidence store 不是 backup repository

backup 本身包含业务数据,访问和保留应按数据分类;治理 evidence 只需记录:

backup label
repository identity
size/count aggregate
coverage
integrity result
restore result

不要把 backup archive 复制到工单附件。

正常生命周期:

created -> retained -> expired -> reviewed -> deleted -> deletion recorded

事件/诉讼 hold:

hold authority
scope
start
reason
access
release authority

hold 不能变成永久保存所有数据的借口;解除后恢复原 retention decision。

redaction 要保留原始与派生关系

安全流程:

original private evidence
  hash A
  restricted access

redacted review copy
  hash B
  derived_from A
  redaction tool/version/rules

直接覆盖 original 会破坏调查能力;把 original 给所有 reviewer 又扩大泄露面。

日志参数与隐私

PostgreSQL 的 statement/parameter logging 可以帮助诊断,也能记录:

  • password reset SQL;
  • token;
  • email/phone;
  • tenant data;
  • health/financial data;
  • application secret。

第 23 章沙箱观察到普通非错误 statement logging 可能保留完整 bind parameter, 且 pgAudit 未安装/preload。这个 gap 在治理层必须:

  • 有 owner;
  • 有整改或接受期限;
  • 限制日志访问;
  • 评审 retention;
  • 避免在 SQL 中传 secret;
  • 对敏感语句/值设计脱敏;
  • 不谎称“审计完整”。

合规是外部控制映射

NIST、ISO、行业或地区监管可以要求:

  • change control;
  • dual authorization;
  • audit generation/review;
  • retention;
  • least privilege;
  • incident evidence;
  • recovery testing。

本章合同可以成为控制 evidence,但不能自行宣布:

compliant
certified
meets every jurisdiction

正式 assessment 还需要:

  • applicable scope;
  • control owner;
  • external requirement version;
  • test procedure;
  • evidence period/sample;
  • deficiency;
  • compensating control;
  • assessor decision。

本章参考 NIST SP 800-53 Rev. 5.1 的 change/audit 思路,不把它当通用认证印章。

24.5.3 用自动检查减少人工表格

自动化检查不变量

最有价值的自动化不是生成更多空表格,而是拒绝错误状态。

本章 validator 检查:

service card
  exact owner functions
  dependencies and failure semantics
  six health layers
  production=false

SLO
  target in (0,1)
  event/time budget arithmetic
  planned maintenance included
  missing=unknown
  correctness/recovery controls

alerts
  objective references
  owner/runbook/action/verification
  symptom/cause/capacity separation
  multiwindow burn set
  actionless candidate rejected

SOP/change
  four capabilities
  precondition/stop/verify/rollback/evidence
  L2/L3 identity separation
  break-glass boundary

evidence
  six categories
  no secrets
  hash/access/retention

交叉引用比格式更重要

JSON 能 parse 不代表治理一致:

SLO objective id
  must exist in observation contract
  must be referenced by alerts

SOP reference run id
  must match retained upstream artifact

upstream production gate
  must remain pending

source hash
  must match exact validator and policy bytes

这类关系很难靠人工逐页检查,适合 CI。

对抗性验证证明 validator 不是摆设

positive fixture 全通过仍可能说明检查从未真正失败。每条关键 policy 要有 negative case:

change target to production
delete platform owner
set SLO target to 1
set missing to healthy
accept actionless page
make capacity a page
remove restore verification
allow one identity to self-approve
allow break-glass to skip evidence
permit secret values
invent upstream run id

本章二十个 mutation 全部必须产生至少一个 failure。若一个 mutation 被接受, validator 即失败。

hash binding

正式 evidence 保存:

source_sha256
  service-card.json
  slo-policy.json
  observation-contract.json
  ...
  validate.py
  review.py
  task.sh

upstream_references
  path
  schema
  run_id
  sha256
  production_gate

这样以后修改合同再验证旧 evidence,会因 source hash drift 失败。不能拿新版 规则解释旧运行而不声明差异。

live gate 与历史证据分开

本章正式运行做两件事:

current:
  rerun chapter-19 read-only deployment gate

historical:
  bind retained ch20–ch23 run summaries by hash

它没有重跑:

  • failover;
  • restore;
  • pool mutation;
  • credential/RLS drill。

因此结论:

current baseline        rechecked
historical references   identity-bound
historical freshness    not automatically renewed
production proof        no

这是 evidence freshness 的重要边界。

secret scanner

review 扫描 private evidence:

  • SCRAM verifier signature;
  • PEM private key;
  • clear password JSON field;
  • credential-bearing PostgreSQL URI。

scanner 不能发现所有 secret,也会有误报/漏报。还需要:

  • source schema allowlist;
  • credential input 与 output 分离;
  • mode 0600
  • no shell args;
  • redacted projection;
  • 人工 review;
  • secret manager policy。

mode 与位置

正式 evidence directory:

  • 新建且为空;
  • umask 077
  • 文件 group/world 不可读;
  • 不进入 repository;
  • private inventory 不复制、不 hash;
  • repository 只保存 secret-free summary。

本章的 governance-run.json 只保留摘要、run id、计数和 source/upstream hash。

自动化不能决定什么

validator 能发现:

target is 1.0
owner missing
hash mismatched
secret field allowed

它不能决定:

  • 99.9% 是否符合真实用户;
  • 250 ms 是否合理;
  • 400 天是否符合法律;
  • 哪个人有组织授权;
  • 一次 data mismatch 的客户影响;
  • 是否应该恢复到某个时间点;
  • 一个 exception 是否值得接受。

这些需要真实 owner 与 authority。自动化的价值是让他们在同一组可信事实上 决策。

将手工表格转换为规则

手工问题:

请确认有回退方案: [x]

更强的机器合同:

rollback_or_roll_forward nonempty
stop_conditions >= 3
verification >= 3
artifact hash exists
approver != executor
preflight age <= policy

不是所有内容都能计数,但能机器判定的部分不应只靠勾选。

CI 分层

PR time
  parse/schema/cross-reference/math/negative cases

pre-deploy
  target/hash/authority/budget/preflight

during deploy
  stop lines + service/component observation

post-deploy
  verification + evidence manifest

scheduled
  owner/route expiry
  restore evidence age
  notification canary
  exception expiry

把所有检查塞进夜间报表会错过执行门槛。

本章正式结果

run id             34909737-527a-460c-927c-d9d71c93aa13
mode               read-only
source artifacts   8 core + scripts/policies
objectives         5
accepted alerts    7
SOPs               4
negative cases     20 rejected
upstream hashes    4
ch19 live gate     accepted-with-exceptions
secret material    absent
production gate    pending

公开摘要见 governance-run.json,正式原始 evidence 位于 私密临时目录,不进入仓库。

Evidence review 清单

  • 结论要证明什么?
  • source、target、collector、time、tool 是否明确?
  • desired/rendered/observed 是否分开?
  • requester/approver/executor 是否可归因?
  • artifact 是否按 hash 绑定?
  • command success 与 postcondition 是否分开?
  • access evidence 是否不含 credential?
  • recovery evidence 是否包含 application verification?
  • SLO numerator/denominator/query/exclusion 是否可复算?
  • missing intervals 是否保留?
  • original 与 redacted copy 是否可追溯?
  • retention 是否有目的、访问、hold 和删除?
  • hash 是否被错误宣传为绝对 authenticity?
  • shared account 是否破坏归因?
  • raw SQL/bind/customer rows 是否最小化?
  • negative cases 是否真的失败?
  • 历史 evidence 的 freshness 是否明确?
  • compliance 结论是否由适用的正式评估给出?

下一节把服务卡、SLO、告警、SOP 和 evidence 串成一个完整的 pg36_shop 治理实验。


上一节:观察与告警契约 · 返回本章目录 · 下一节:实战:把 pg36_shop 纳入服务治理 · 查看全书目录 · 查看索引中心

24.6 实战:把 `pg36_shop` 纳入服务治理

本节把全章压成一条可重放的 L0 证明:

machine-check service card
  -> calculate SLO and control objectives
      -> bind observation sources and missing semantics
          -> accept actionable alerts
              -> reject actionless page
                  -> bind four SOPs to upstream drills
                      -> enforce high-risk authority
                          -> enforce evidence retention/redaction
                              -> rerun current ch19 deployment gate
                                  -> hash-bind ch20–ch23 summaries
                                      -> reject 20 adversarial mutations

实验不会:

  • 创建或修改 PostgreSQL 对象;
  • 改 PgBouncer/HAProxy;
  • 切换 leader;
  • 运行 restore;
  • 修改 role/credential/HBA/TLS;
  • 部署 VMAlert rule;
  • 发送真实 page;
  • 删除或重置数据。

all 在本章表示“采集只读 gate 并验证全部合同”,不是“执行所有被引用的 高风险演练”。

实验合同:

24.6.1 发布服务卡、SLO、责任人与升级路径

目标环境

service             pg36_shop
target              pg36-l2-vagrant/pg-test
environment         l2-sandbox
data                synthetic teaching data
production traffic  false
Pigsty              v4.5.0
PostgreSQL major    18
host count          4
PG member count     4 across pg-meta and pg-test

服务卡明确:

catalog_status       teaching-reference
production_tier      false
production_slo       false

因此 validator 会拒绝任何把它改成 production SLO 的 mutation。

文件布局

static/labs/ch24/
├── requirements.json
├── service-card.json
├── slo-policy.json
├── observation-contract.json
├── alert-candidates.json
├── sop-catalog.json
├── change-policy.json
├── evidence-retention.json
├── governance-adr.md
├── dependency-map.mmd
├── lab-contract.md
├── negative-cases.json
├── build_evidence.py
├── validate.py
├── review.py
├── task.sh
└── governance-run.json

核心合同八份,其他文件负责解释、验证和保存公开摘要。

服务卡

service-card.json 记录:

customer journeys   place-order, read-order
owner functions     service, data, platform, security/privacy
dependencies        6
health layers       6
escalation           SEV-1, SEV-2, ticket
known gaps          6

四个 owner:

function role id accountable
service shop-service-owner journey、SLO、release、业务决定
data shop-data-owner 语义、保留、质量、隐私分类
platform database-platform-owner PG/Pigsty、容量、恢复、执行
security/privacy security-privacy-duty access、例外、披露、隐私

这些是 role contracts,没有写个人名字。route://... 也只是教学 route id, 不声称有真实 pager。

依赖图

user
  -> pg36_shop application boundary
      -> HAProxy / PgBouncer
          -> PostgreSQL pg-test
              -> Patroni / etcd
              -> pgBackRest / WAL repository

all observable through
  VictoriaMetrics / VictoriaLogs / VMAlert / Alertmanager / Grafana

Mermaid 源文件: dependency-map.mmd

每项依赖带 failure semantics。例如:

victoria-observability fails
  -> service state becomes unknown
  -> do not interpret missing telemetry as healthy

六层健康

process
endpoint
authentication
transaction
correctness
durability_and_recovery

所有层的 sufficient_for_service_health=false,服务卡另有:

"service_health_requires_all_layers_and_user_contract": true

validator 会把第一层改成 true,确认“process alive 即服务健康”被拒绝。

SLO policy

slo-policy.json 定义:

ID kind target
SLO-AVAILABILITY ratio 99.9% / rolling 28d
SLO-LATENCY ratio 99% under 250 ms / rolling 28d
SLO-FRESHNESS ratio 99% under 5 s / rolling 28d
CTRL-CORRECTNESS control zero unexplained mismatch
CTRL-RESTORE-READINESS control passing isolated restore ≤ 90d

算术验证:

window seconds              2,419,200
availability target         0.999
sample eligible events      10,000,000
sample error budget events  10,000
equivalent time budget      2,419.2 s / 40.32 min

Python validator 使用容差重新计算,而不是信任 JSON 中的答案。

exclusion

三个默认排除:

  • admission 前的语法错误;
  • 正确拒绝的未授权身份;
  • admission 前客户端断开且没有 server outcome。

明确:

planned_maintenance_excluded  false
retroactive_exclusion_allowed false

一个新例外必须记录 id、时间、operation、independent approver、reason 和 before/after count。

error-budget state

healthy      > 50%
watch        > 25% and <= 50%
constrained  > 0%  and <= 25%
exhausted    <= 0%

每个状态都有 action。validator 把 exhausted actions 清空,确认“没有后果的 预算”被拒绝。

只做合同 lint

不连接任何 sandbox:

static/labs/ch24/task.sh lint

输出:

status=validation-ok
schema=pg36-ch24-validation-report-v1
production_ch24_gate=pending
status=validation-ok
schema=pg36-ch24-negative-report-v1
counterexamples=20-rejected
production_ch24_gate=pending
status=lint-ok
counterexamples=20-rejected
mutation=none
production_ch24_gate=pending

lint 使用私密临时目录保存报告,并在结束时删除;不产生 repository 文件。

24.6.2 为备份、切换、权限和发布建立 SOP

四份 SOP

sop-catalog.json

ID capability risk owner / approver
SOP-BACKUP-RESTORE backup and restore L3 platform / data
SOP-ROLE-CHANGE switch/failover L3 platform / service
SOP-ACCESS-ROTATION access/credential L2 security / platform
SOP-SCHEMA-RELEASE schema release L2 service / platform

owner 与 approver 不同。每份包含:

  • trigger;
  • exact target;
  • blast radius;
  • prerequisites;
  • procedure phases;
  • stop conditions;
  • verification;
  • rollback/roll-forward;
  • evidence;
  • review interval。

恢复 SOP

输入:

source cluster
backup repository
recovery objective
target time/name
isolated destination
application invariants
space/WAL/authority

停止:

  • identity/target 模糊;
  • destination 可接 production traffic;
  • WAL/integrity 失败;
  • 验收未事先固定。

通过:

  • target marker 存在;
  • post-target marker 不存在;
  • database identity 正确;
  • recovered server 已停止;
  • timing 只称 sandbox observation。

validator 会把 verification 替换为:

backup command exited zero

并拒绝。

role change SOP

先区分:

planned switchover
unplanned failover

共同停止线:

  • 多写主可能;
  • DCS/fencing unknown;
  • candidate 不符合 durability policy;
  • unknown writes 不能核对。

验证:

one leader
timeline progression
acknowledged rows present
unknown classified
pool routes current leader

rollback 文案刻意写:

prefer safe roll-forward to one fenced leader;
a planned return is a new change

timeline 已推进后,不能把“切回”当撤销历史。

access rotation SOP

步骤:

new secret version out-of-band
  -> new authentication test
      -> bounded client/pool migration
          -> old new-session auth rejected
              -> revoke login/credential
                  -> explicit existing-session decision
                      -> verify final role and secret-free evidence

若 suspected disclosure,不能 rollback 到旧 secret,只能再次向前轮换。

schema release SOP

expand schema
  -> old/new app compatible
      -> bounded idempotent backfill
          -> constraint/reconciliation
              -> feature switch
                  -> contract old representation later

停止:

  • lock 超时;
  • WAL/lag/disk/budget 越界;
  • old app 提前不兼容;
  • backfill 不可恢复或不幂等。

上游实验绑定

三份 SOP 引用第 20、21、23 章;第 22 章作为入口/池证据也在本章 upstream manifest 中绑定。

run id SHA-256 production
ch20 475e9b47-bc35-4687-87da-012f1d5ea455 602e932b...9d9a5d pending
ch21 run_20260729T201040Z_961665aa c0b3589d...a212ca pending
ch22 87a63891-d6bf-46b3-bb65-d70a8d7bac3a 444a6521...e6a2 pending
ch23 64b857a6-8d8f-46e2-9462-3f097a95a69f e1548658...cb59 pending

完整 digest 见 governance-run.json

绑定证明:

this governance contract references these exact public summaries

不证明:

raw private evidence is embedded
the runs are fresh today
the actions were repeated in chapter 24
the sandbox results are production performance

变更政策

change-policy.json 固定 L0–L3。

L2/L3:

requester_may_approve                 false
approver_may_execute                  false
requester_approver_executor_distinct  true
shared_accounts_allowed               false
independent_approver_qualified        true
exact_target_and_blast_radius         true
machine_preconditions                 true
confirmation_binds target/action/time true

break-glass:

skip normal wait          allowed
skip target/evidence      forbidden
time-bounded identity     required
after-action review       required
credential rotation       required

正式 L0 运行

需要第 19 章 private mode-0600 inventory:

export PG36_CH19_INVENTORY=/absolute/private/baseline.yml
export PG36_EVIDENCE_DIR=/absolute/private/new-empty/ch24-run

static/labs/ch24/task.sh all

runner:

1 reject nonempty evidence directory
2 run ch19 task.sh all
3 build governance-evidence.json
4 hash current source files
5 bind ch20–ch23 summaries
6 positive validation
7 negative mutation validation
8 review permissions and secret patterns

没有 reset action。

当前第 19 章 gate

正式运行观测:

captured        2026-07-29T21:57:40Z
Pigsty          v4.5.0
PostgreSQL      major 18
hosts           4
PG members      4
sandbox L2      accepted-with-exceptions
exceptions      6
production      pending
mutation        none

六项例外:

  • shared hypervisor;
  • single etcd;
  • single backup target;
  • virtual storage;
  • sandbox inventory secret handling;
  • lab resource floor。

再次通过 gate 没有消除这些例外。

正式 evidence

run id       34909737-527a-460c-927c-d9d71c93aa13
captured     2026-07-29T21:57:40.539Z
mode         read-only-contract-and-live-baseline-binding
mutation     none

private evidence 保存:

  • ch19 current capture;
  • governance evidence;
  • positive report;
  • negative report;
  • review output。

公开仓库只保存 governance-run.json

24.6.3 输出观察契约,并拒绝没有动作的告警候选

观察契约

observation-contract.json 为五个目标绑定:

source
metric type
good/total selector
dimensions
recording rule
query template
missing semantics
fallback

Pigsty component labels:

cls / ins / ip

application labels:

service / operation_class / environment

禁止:

customer_id / tenant_id / order_id / raw_sql / error_message

component telemetry

四组:

ID sources purpose
PG-COMPONENT-ACTIVITY activity/database/io concurrency/error/I/O diagnosis
PG-COMPONENT-REPLICATION replication/receiver/LSN replay and WAL risk
PG-COMPONENT-ARCHIVE archiver/pgBackRest recovery-path risk
PIGSTY-CORRELATION Victoria stack correlate PG/pool/LB/host/log

它们不替代 user SLI。

accepted alerts

alert-candidates.json

alert class route
PG36ShopAvailabilityFastBurn symptom page SEV-1
PG36ShopAvailabilitySlowBurn symptom page SEV-2
PG36ShopAvailabilityBudgetTicket symptom ticket
PG36ShopCorrectnessMismatch integrity page SEV-1
PG36ShopFreshnessFastBurn symptom page SEV-2
PG36ShopCapacityHorizon capacity ticket
PG36MonitoringPathBroken metamonitoring page SEV-2

其中:

symptom/integrity pages  4
metamonitoring pages     1
tickets                  2

所有 accepted item 都有:

  • user impact;
  • owner function;
  • route;
  • runbook;
  • first safe action;
  • verification;
  • dashboard;
  • missing semantics;
  • silence policy;
  • test id;
  • review expiry。

rejected alert

PostgresInstanceDownWithoutAction

字段:

proposed_route     page
decision           rejected
user_impact        null
owner              null
runbook             null
first_safe_action  null

replacement:

correlate endpoint and user-event symptoms;
retain instance state on topology dashboard

这不是漏填;它是一个特意保留的反例。若有人将 decision 改为 accepted, validator 失败。

三个 diagnostic-only causes

PG36ReplicaReplayDistance
PG36PoolQueueDepth
PG36HostCpuHigh

都不 page。它们进入 dashboard,只有 correlated user/integrity/durability 合同触发后才帮助定位。

metamonitoring

要求:

  • application series freshness;
  • VMAlert evaluation failures;
  • Alertmanager notification canary;
  • VictoriaMetrics query/ingestion;
  • external blackbox probe。

missing_is_healthy=false

二十个反例

ID mutation 被哪类不变量拒绝
N01 宣称 production SLO scope
N02 删除 platform owner ownership
N03 process 即服务健康 health chain
N04 SLO target = 100% ratio target
N05 排除 planned maintenance exclusion
N06 instance health = user SLI measurement
N07 missing = healthy missing semantics
N08 exhausted 无 action budget policy
N09 availability series 缺失算健康 observation
N10 允许 unbounded labels cardinality/privacy
N11 page 无 first action actionability
N12 cause 直接 page alert class
N13 capacity 直接 page route
N14 接受 actionless instance page rejection
N15 backup exit 0 即 restore recovery evidence
N16 一人申请批准执行 authority
N17 break-glass 跳过 target/evidence emergency boundary
N18 access evidence 允许 secret privacy
N19 伪造 ch21 run id upstream identity
N20 沙箱切换称 production proof claim boundary

正式结果:

case_count      20
rejected_count  20
failure_count   0

review 输出

status=review-ok
run_id=34909737-527a-460c-927c-d9d71c93aa13
service_card=complete
objectives=3-ratio+2-control
accepted_alerts=7
actionless_alerts=1-rejected
sops=4
counterexamples=20-rejected
ch19_live_gate=accepted-with-exceptions
upstream_runs=4-bound-by-hash
mutation=none
production_ch24_gate=pending
secret_material=absent

为什么 production 仍 pending

机器验证通过,不等于生产批准。仍缺:

  1. 真实 service/data/platform/security owner;
  2. 真实 on-call schedule 与 notification test;
  3. 真实用户 traffic 的 eligible/good classifier;
  4. 生产 failure domains;
  5. 生产容量和 workload;
  6. 多次切换/恢复的分布,而非一次观察;
  7. 真实 RTO/RPO 谈判;
  8. 法律/隐私 retention authority;
  9. production secret/audit 路径;
  10. 第 25 章真实 instrumentation/rules/dashboards;
  11. 生产等价环境中的重复演练;
  12. 正式 exception 与 expiry。

所以本章结论:

governance contract       pass
machine validation        pass
adversarial validation    pass
current sandbox baseline  accepted-with-exceptions
upstream identity binding pass
real alert delivery       not run
production SLO evidence   absent
production approval       pending

重验已有 evidence

export PG36_EVIDENCE_DIR=/absolute/private/existing/ch24-run
static/labs/ch24/task.sh verify
static/labs/ch24/task.sh review

修改任何受 hash 保护的 policy/script 后,旧 evidence 验证会失败。应:

retain old evidence with old source
create new empty evidence directory
run current contract again
compare decisions explicitly

不能覆盖旧 evidence 让历史“自动符合”新政策。

本章验收

  • service card 有 4 owner、6 dependencies、6 health layers;
  • 3 个 ratio SLO 和 2 个 control objective 可计算;
  • planned maintenance 未被隐藏;
  • missing telemetry 不算健康;
  • error budget 真实影响 change policy;
  • 四类 SOP 有停止线与验证;
  • L2/L3 权力分离;
  • break-glass 仍需 target/evidence;
  • 五个 observation source 有 query/missing/fallback;
  • 7 个 accepted alerts 可行动;
  • cause、capacity 与 symptom 分开;
  • actionless page 明确拒绝;
  • 六类 evidence 不允许 secret;
  • ch19 live gate 重新只读通过;
  • ch20–23 run 按 id/hash 绑定;
  • 20 个 adversarial mutations 全拒绝;
  • production gap 与 pending gate 保留。

第 25 章的任务不是重新发明这些语义,而是把它们实现为真实的应用 metric、 Pigsty recording/alert rules、dashboard、notification route 和 rule tests。


上一节:证据、审计与合规 · 返回本章目录 · 下一章:望闻问切:监控体系与可观测诊断 · 查看全书目录 · 查看索引中心