跳至内容
24.2 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%
SLOSLI 的内部目标28 天至少 99.9%
SLA对外承诺与后果未达 99.9% 触发服务补偿
error budget目标允许的 bad events0.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

事件分类:

情形eligiblegood原因
合法请求,提交并返回成功完整成功
合法请求,应用 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{\text{eligible events with latency} \le 250\text{ ms}} {\text{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 + reconciliationconsume budget/page
latency是否足够快edge histogramconsume budget/page
freshness已提交状态何时可见commit-token probeconsume budget/route change
correctness数据是否可信invariant reconciliationfreeze/page
restore readiness能否恢复isolated drill evidence ageblock risky change

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

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

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

一条请求的观测点:

client
  -> CDN / gateway
      -> application
          -> HAProxy
              -> PgBouncer
                  -> PostgreSQL
测量点能覆盖看不到
PostgreSQLSQL、transaction、lock、I/Ogateway/app/network errors
PgBouncerpool wait、server assignmentapp correctness
applicationuser operation、business outcomeclient edge/network
gatewayuser-visible HTTP resultcommit correctness unless correlated
client/syntheticend-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$:

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

本章:

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

观察到 7,500 bad:

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

剩余 25%,进入 constrained 边界。

等价时间预算

时间可用性解释:

$$ 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 badburn
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 与变更治理 · 查看全书目录 · 查看索引中心

最后更新于