← 中文资源

简体中文版本

错误预算燃烧率

燃烧率衡量 SLO 错误预算相对计划的消耗速度;多窗口告警在“花超预算期”时触发——快燃立即呼页,慢燃开单跟进。

阅读英文原文

概念定义

99.9% 的 30 天 SLO 约给 43 分钟容许停机。告警要回答的不是“错误率高吗”,而是“预算烧得是否快到撑不满周期”。燃烧率回答这个。

工作机制

计算观测坏事件率对预算率的比值:14.4 倍燃烧会在两天烧光 30 天预算——呼页;1 倍燃烧恰好按计划消耗——正常。多窗口把短窗的敏感与长窗的确认配对:两者同时超阈才是持续快燃,仅短窗超阈只是毛刺。

使用场景

在定义了诚实 SLI/SLO 与真实错误预算之后采纳——在那之前,燃烧告警度量的是无人同意过的预算。配套政策:预算烧尽则冻结功能开发转向可靠性,否则数字只是装饰。

常见误区

燃烧率不是花哨的错误率:同样的 0.1%,在 99.99% 目标下是紧急事件,在 99% 下是噪声。单窗口要在误报与迟钝之间二选一,配对窗口才能验证持续燃烧。窗口算术必须匹配预算周期——阈值是推导出来的不是拍的;SLI 选择才是主导:度量服务器在线而用户在挨失败,烧的是一笔看不见的预算。分母要按请求归一而不是按分钟,否则低流量深夜会伪造燃烧。

代码示例

from dataclasses import dataclass, field
import random

@dataclass
class Span:
    name: str
    duration_ms: float
    error: bool = False

@dataclass
class Policy:
    slow_ms: float = 500.0
    keep_errors: bool = True,
    fallback_rate: float = 0.01

def decide(spans: list['Span'], policy: Policy, rng: random.Random | None = None) -> tuple[bool, str]:
    rng = rng or random.Random()
    if not spans:
        return False, 'empty'
    if policy.keep_errors and any(s.error for s in spans):
        return True, 'error'
    slowest = max(s.duration_ms for s in spans)
    if slowest >= policy.slow_ms:
        return True, 'slow'
    if rng.random() < policy.fallback_rate:
        return True, 'sampled'
    return False, 'dropped'
package main

import (
	"context"
	"fmt"
	"time"
)

type Step struct {
	Name string
	Cost time.Duration
}

func runSteps(ctx context.Context, steps []Step) error {
	for _, step := range steps {
		if err := ctx.Err(); err != nil {
			return fmt.Errorf("before step %q: %w", step.Name, err)
		}
		if deadline, ok := ctx.Deadline(); ok {
			remaining := time.Until(deadline)
			if remaining < step.Cost {
				return fmt.Errorf(
					"step %q needs %s but only %s remains: %w",
					step.Name,
					step.Cost,
					remaining.Round(time.Millisecond),
					context.DeadlineExceeded,
				)
			}
		}

		timer := time.NewTimer(step.Cost)
		select {
		case <-ctx.Done():
			timer.Stop()
			return fmt.Errorf("step %q interrupted: %w", step.Name, ctx.Err())
		case <-timer.C:
			fmt.Printf("completed %q in %s\n", step.Name, step.Cost)
		}
	}
	return nil
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond)
	defer cancel()

	steps := []Step{
		{Name: "load-config", Cost: 20 * time.Millisecond},
		{Name: "fetch-profile", Cost: 25 * time.Millisecond},
		{Name: "render-report", Cost: 60 * time.Millisecond},
	}
	if err := runSteps(ctx, steps); err != nil {
		fmt.Println("stopped:", err)
	}
}