24.2 SLI、SLO 与错误预算
“数据库必须高可用”不是 SLO。它没有说明:
- 测什么;
- 从哪里测;
- 什么算好;
- 什么进入分母;
- 多长窗口;
- 允许多少失败;
- 没有数据怎么办;
- 未达标后改变什么。
可执行的 SLO 至少是:
本节从用户事件开始,而不是从 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}} $$
关键不是公式,而是 event classification。
可用性:成功必须能核对
本章的下单可用性:
事件分类:
| 情形 | eligible | good | 原因 |
|---|---|---|---|
| 合法请求,提交并返回成功 | 是 | 是 | 完整成功 |
| 合法请求,应用 500 | 是 | 否 | 用户失败 |
| 合法请求,连接在 commit 后断开,尚未核对 | 是 | 否,直到核对 | 不把 unknown 假装成功 |
| 合法请求,返回成功但查不到订单 | 是 | 否 | 错误承诺 |
| 合法请求,创建两张订单 | 是 | 否 | 幂等性失败 |
| 语法不符合已发布 API | 否 | 不适用 | 未被服务接纳 |
| 正确拒绝无权限身份 | 否 | 不适用 | 安全合同正常工作 |
| 应有权限却因配置错误被拒绝 | 是 | 否 | 服务失败,不是“安全排除” |
| 服务接纳后客户端取消 | 是 | 依实际结果 | 不能事后从分母删除 |
HTTP 2xx、SQL 无异常或 transaction commit 任一单独都不够。good event 应当
表达用户获得的承诺。
不要用“成功查询数”混合所有操作
高流量健康查询会淹没低流量关键操作:
所以 operation class 必须按后果拆分:
place-order;read-own-order;admin-report;background-reconcile。
不能为得到好看的总数,把不同用户旅程放入同一个分母。
延迟:问“多少事件够快”,不要只看平均值
本章延迟目标:
相应 SLI:
$$ \text{latency SLI}
\frac{ \left|\left{e \in E: good(e) \land latency(e) \le 250\text{ ms} \right}\right| }{ |E| } $$
其中 是所有 eligible events;快速失败虽然耗时很短,也不进入分子。
快速失败不应自动成为 latency good。若一个请求 5 ms 返回 500,它在用户旅程 上既不可用,也没有完成目标动作。
平均延迟会掩盖尾部:
平均值看似低于 250 ms,但最慢用户等了十秒。比例 SLO 或分位数更能表达尾部。 用于预算时,固定阈值的 good/eligible 比例比“p99 的月平均”更容易严格累计。
histogram 边界必须在采集前确定
若 histogram 没有 0.25 秒 bucket,事后无法从聚合数据精确回答“多少请求
低于 250 ms”。因此 SLO 先于 instrumentation:
这正是本章先写观察契约、下一章才实现指标的原因。
正确性:不要用 99.9% 原谅数据错误
某些正确性可以定义为比例,例如非关键搜索结果质量。但本章的订单不变量:
- 一个 idempotency token 只对应一张订单;
- tenant A 看不到 tenant B;
- 金额和状态迁移合法;
- acknowledged order 不丢失;
使用 control objective:
这不是声称软件永远不会出错。它是规定“发现一条此类错误时,组织不能用剩余 错误预算继续正常发布”。
正确性 control 需要:
- 明确覆盖哪些 invariant;
- 固定 input boundary;
- 记录 job/version/query hash;
- 区分 known exception 与 unexplained mismatch;
- 防止 repair 覆盖原始证据;
- 定期证明 reconciliation 本身仍运行。
数据新鲜度:必须与某次 commit 关联
“replica lag 小于 5 秒”有至少三种含义:
- 接收 WAL 与 primary 的 byte distance;
- replay 记录的 timestamp 距当前 wall clock;
- 用户的一次已提交写入在读取路径上何时可见。
用户关心第三种。前两种用于诊断。
本章 freshness event:
流程:
这样能覆盖:
- WAL 传输和 replay;
- HAProxy route;
- PgBouncer;
- query path;
- cache;
- 应用序列化。
now() - pg_last_xact_replay_timestamp() 在空闲时会增长,不能替代这个 probe。
恢复就绪不是可用性 SLO
服务连续运行 28 天,不代表能从灾难恢复。恢复 control:
backup completed 只能作为输入证据。第 21 章已经证明:
缺任一阶段,不能把 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 测量点、统计窗口与排除条件
测量点越靠近用户,覆盖越完整
一条请求的观测点:
| 测量点 | 能覆盖 | 看不到 |
|---|---|---|
| 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 | 所有真实用户分布 |
通常使用:
若应用不能立刻增加指标,可以暂用 synthetic probe,但要记录 coverage gap。不要 因为 PostgreSQL 指标更容易获得,就悄悄把 SLO 测量点向内移动。
事件记录与时序指标各有用途
原始事件适合核对:
时序 counter/histogram 适合在线聚合与告警。不要把每个 order id 放进 metric label;需要 drill-down 时,用受控 event/log store 通过 correlation id 连接。
一个教学用关系表:
生产上不一定用 PostgreSQL 保存所有事件;这里用 SQL 展示语义。
过去 28 天:
good IS NULL 若表示 unknown,预算查询应暂按 bad 或单独阻断,而不能由
count(*) FILTER (WHERE good) 自动消失后仍声称完整。
延迟:
这里要求 availability good 且足够快,避免快速错误通过延迟目标。
滚动窗口与日历报告是两件事
本章采用滚动 28 天:
优点:
- 每天都代表同样长度;
- 没有月初“预算重置”错觉;
- 适合持续 alert/budget decision。
28 天是四周,不是自然月。财务、客户或合规可能要求 calendar month/quarter 报告,应单独生成,不要把两个窗口混成一个数字。
推荐节奏:
Google SRE 的 SLO 实施章节也讨论四周滚动窗口、周汇总和季度报告。这些是实践 起点,不是对所有组织的强制周期。
长窗口防止遗忘,短窗口缩短响应
只有月窗:
- 稳定但反应慢;
- 一次快速事故可能在总体比例中暂时不显眼。
只有 5 分钟窗:
- 反应快;
- 流量低时一个错误就剧烈波动;
- 容易被短暂 blip 打扰。
multiwindow 同时要求长窗和短窗超过同一 burn threshold:
这比简单 error_rate > 1% for 5m 更贴近 SLO 后果。
排除条件应在事件发生前定义
本章默认排除:
- 在 admission 前被正确拒绝的语法错误;
- 正确拒绝的越权身份;
- 服务接纳工作前的客户端取消。
默认不排除:
- 计划维护;
- 发布导致的错误;
- 内部依赖故障;
- operator mistake;
- 容量不足;
- “已知问题”;
- 未核对的 unknown result。
原因很简单:用户并不会因为中断有 change ticket 就得到服务。
若产品真的有公开维护窗口,应在服务合同中定义独立承诺,而不是事故后从事件 表删除数据。
例外必须不可追溯篡改
一个 SLO exception 至少记录:
禁止:
这种做法破坏了 SLO 作为决策工具的价值。
telemetry 缺失不能等于零错误
PromQL 查询经常在 series 消失时返回空向量。若 dashboard 把空值渲染为 0, 会出现:
每个 SLI 必须定义:
- expected series cadence;
- no-traffic 与 missing telemetry 如何区分;
absent()或 freshness check;- 独立 synthetic fallback;
- metamonitoring route。
本章统一原则:
低流量服务不能照搬高流量阈值
若 5 分钟只有两个请求,一个失败就是 50% error rate。可以:
- 运行 end-to-end synthetic probe;
- 延长窗口;
- 合并后果相同的 operation,但不能用无关流量稀释;
- 使用明确评审的 time-based 或 user-minute SLI;
- 对关键 batch 用“最近两次应完成周期”定义 freshness;
- 将零容忍 correctness control 独立出来。
降低告警灵敏度不能变成不观察低流量关键路径。
标签维度既要可诊断,也要有界
建议:
谨慎:
禁止无界:
Pigsty 组件关联使用 cls、ins、ip;应用 SLI 使用 service 维度。不要把
实例 identity 当成服务 identity。
24.2.3 错误预算如何约束变更速度
预算不是允许主动制造错误
目标 99.9% 不表示可以计划使用 0.1% 伤害用户。预算用于在可靠性与变化之间 做有数据的选择:
100% 通常不是合适的 ratio target:
- 它没有 error budget;
- 任一测量噪声都成为违约;
- 团队会隐藏错误或停止变化;
- 它仍不能保证 correctness 和 recovery。
“永不丢数据”这样的要求应拆成 durability/control design,而不是伪装成 100% request ratio。
事件预算
若目标 $T$,eligible event 数 $N$:
本章:
观察到 7,500 bad:
剩余 25%,进入 constrained 边界。
等价时间预算
时间可用性解释:
28 天、99.9%:
不要把它用于篡改 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:
长窗证明这不是一个无关紧要的点;短窗证明问题仍在。若使用 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 重新谈判,不能追溯 |
错误预算影响速度,不取消风险管理。
预算的所有者
不能由开发团队单方面改分母,也不能由 DBA 单方面降低服务目标。
防止四种 gaming
事后改变 eligibility
错误发生后把它归类为“不算用户请求”。
把事故改名 maintenance
变更单不能让用户中断消失。
把测量点移到内部
gateway 错误很多时,改看 SELECT 1。
用大流量稀释关键路径
把失败的下单和成功的健康检查合并。
防线:
- versioned SLO policy;
- event classifier tests;
- before/after counts;
- independent approval;
- immutable exception;
- change log;
- 定期抽样原始结果。
从 SLO 到变更门禁
变更请求要读取当前预算:
门禁输入必须固定时间:
审批后若 fast-burn page 触发,执行器应自动停止,而不是拿旧截图继续。
本章 SLO 文件
完整机器可读政策:
重点不是 JSON 语法,而是同一 objective id 在三份文件中保持一致:
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 与变更治理 · 查看全书目录 · 查看索引中心