SRE и надёжность Бюджет ошибок: арифметика и что она меняет в решениях
0%

Бюджет ошибок: арифметика и что она меняет в решениях

Бюджет ошибок: арифметика и что она меняет в решениях

В прошлой главе мы выбирали показатель и назначали ему цель: «99,9% запросов к оформлению заказа завершаются успешно за 30 дней». Прочитаем ту же строчку задом наперёд. Если 99,9% запросов должны быть успешными, то 0,1% запросов разрешено быть неуспешными. Это не дефект формулировки и не поблажка. Это ресурс: конечная, измеримая, восполняемая величина, которую можно потратить на что угодно — на неудачную выкатку, на эксперимент, на миграцию базы, на новую фичу с непроверенным кодом. Она называется бюджет ошибок (error budget).

Вся ценность понятия в одном сдвиге. До бюджета разговор о надёжности звучит так: «надо, чтобы не падало» против «надо быстрее выпускать» — два лозунга, каждый из которых прав, и побеждает тот, кто громче. После бюджета: «у нас осталось 11 минут из 43, а до конца месяца две недели» — и спорить особо не о чем, потому что число одно и оно у всех перед глазами. Эта глава — про арифметику, не про лозунги.

Одна формула

$$ \text{ErrorBudget} = 1 - \text{SLO} $$

Дальше начинаются следствия, и они интереснее формулы. Бюджет измеряется в тех же единицах, что и SLI: временной SLI («доля хороших минут») даёт бюджет в минутах, событийный («доля хороших запросов») — в запросах. Эти две арифметики дают разные ответы на один и тот же инцидент, и разницу мы посчитаем ниже.

Бюджет привязан к окну. «99,9%» без окна — бессмысленная строка: за час, за месяц или за десятилетие? Стандартный выбор — 28 или 30 дней; квартальные и годовые окна встречаются в контрактах, но для инженерных решений слишком инертны.

Сколько это в минутах

Считаем в лоб: длительность окна умножаем на долю, которую разрешено потерять, $D = (1 - S) \cdot T$. Тридцатидневный месяц — это $30 \cdot 24 \cdot 60 = 43\,200$ минут, год — $365 \cdot 24 \cdot 60 = 525\,600$ минут.

SLO Доля потерь Сутки (1440 мин) Неделя (10 080) 30 дней (43 200) Год (525 600)
99% 1% 14 мин 24 с 1 ч 40 мин 48 с 7 ч 12 мин 3 сут 15 ч 36 мин
99,5% 0,5% 7 мин 12 с 50 мин 24 с 3 ч 36 мин 1 сут 19 ч 48 мин
99,9% 0,1% 1 мин 26 с 10 мин 5 с 43 мин 12 с 8 ч 45 мин 36 с
99,95% 0,05% 43 с 5 мин 2 с 21 мин 36 с 4 ч 22 мин 48 с
99,99% 0,01% 8,6 с 1 мин 0 с 4 мин 19 с 52 мин 34 с
99,999% 0,001% 0,86 с 6,0 с 25,9 с 5 мин 15 с

Проверим одну строку руками, чтобы дальше доверять таблице (SLO = 99,9%, окно 30 дней): (1 - 0,999) * 43 200 = 43,2 минуты, а 0,2 минуты — это 12 секунд, итого 43 минуты 12 секунд.

43 минуты в месяц — это очень мало и одновременно очень много. Мало, если у вас случился один-единственный фейловер базы: типичное переключение реплики с прогревом кэшей займёт 5–15 минут, и четверть бюджета исчезнет. Много, если сравнивать с интуитивным ощущением «мы почти не падаем»: команды, уверенные в своей стабильности, при первом честном измерении обычно обнаруживают, что не держат даже 99,9%.

Месячный бюджет ошибок при SLO 99,9%

Пять девяток: арифметика неприличия

Число «99,999%» встречается в презентациях гораздо чаще, чем в реальности. Разберём, почему оно почти никому не нужно, — тремя независимыми аргументами.

Аргумент первый: человек не успевает

25,9 секунды в месяц — это весь простой, целиком. Теперь разложим типичный инцидент по времени:

обнаружение (сработал алерт по 5-минутному окну)   30 с — 5 мин
подтверждение (дежурный проснулся, открыл ноутбук)   2 — 10 мин
диагностика                                          5 — 60 мин
устранение (откат, переключение, рестарт)            1 — 30 мин

Даже идеально отлаженная человеческая реакция — это минуты. Значит, пять девяток означают, что человека в контуре реакции нет вообще: всё восстановление автоматическое и укладывается в секунды. Это не «команда старается лучше», это другая архитектура: активно-активная топология, автоматический выброс нездорового экземпляра, ноль ручных шагов в откате. Дежурство там нужно не чтобы чинить во время инцидента, а чтобы разбираться, почему не справилась автоматика.

Обратная арифметика полезнее прямой: пусть среднее время восстановления (MTTR) — 10 минут, сколько инцидентов в год вы можете себе позволить?

SLO Бюджет за год При MTTR 10 мин При MTTR 60 мин
99,9% 525,6 мин 52 инцидента 8,7 инцидента
99,95% 262,8 мин 26 инцидентов 4,4 инцидента
99,99% 52,6 мин 5 инцидентов 0,9 инцидента
99,999% 5,3 мин 0,5 инцидента невозможно

Строка «99,999% при MTTR 60 минут» — не «трудно», а арифметически невозможно: один часовой инцидент раз в десять лет уже нарушает годовое SLO. Прежде чем обещать девятки, посмотрите на свой реальный MTTR — это гораздо более честный вход в разговор, чем желаемая цифра.

Аргумент второй: зависимости

Ваш сервис не один: запрос проходит через балансировщик, шлюз, сам сервис, базу, кэш. Если отказы независимы, доступность цепочки — произведение $A_{\text{chain}} = \prod_{i=1}^{n} A_i$.

Пять компонентов по 99,9% каждый дают 0,999^5 = 0,995010, то есть 99,501%: потери 0,499%, или 0,00499 * 43 200 = 215,6 минуты в месяц (3 ч 36 мин). Пять «трёхдевяточных» кубиков в сумме дают чуть лучше двух с половиной девяток. Обратная задача нагляднее: чтобы цепочка из пяти компонентов давала 99,9%, каждый компонент должен держать 0,999^(1/5) = 0,99980, то есть 99,98% — по 8,64 минуты в месяц на компонент.

А теперь посмотрите на SLA ваших провайдеров. Публичные обязательства облаков — обычно 99,9–99,99% на региональный сервис и заметно ниже на отдельную виртуальную машину: например, Amazon EC2 SLA даёт 99,99% на регион и 99,5% на отдельный инстанс, Amazon S3 SLA — 99,9%, Google Compute Engine SLA — похожие цифры. Обещать пользователю 99,999%, стоя на фундаменте с 99,9%, можно только если вы построили над этим фундаментом полноценную мультиоблачную избыточность — и оплатили её.

Аргумент третий: пользователь всё равно не увидит

До вашего бэкенда запрос идёт через мобильную сеть, Wi-Fi в кафе, DNS-резолвер провайдера и браузер с расширениями. Доступность мобильной связи для конкретного абонента — заметно хуже 99,9%: достаточно поездки в метро. Если пользовательский путь целиком даёт 99%, разница между вашими 99,99% и 99,999% для него неотличима от шума. Это прямое следствие той же формулы произведения: улучшать самое надёжное звено цепи бесполезно.

Похожая ловушка разобрана в главе о локальной оптимизации: улучшение части системы, которая не является ограничением, не улучшает систему. Вывод не «девятки — зло», а «каждая следующая девятка должна быть оплачена чем-то, кроме гордости». Практическое правило: начните с 99,9%, честно измеряйте и повышайте цель только тогда, когда есть конкретный плательщик — контракт с неустойкой, регуляторное требование, измеренный отток. Понижать цель тоже нормально: SLO 99,5% для внутреннего аналитического сервиса — не поражение, а признание, что 3,5 часа простоя в месяц никого не убьют, а деньги на резервирование нужны в другом месте.

Временной бюджет против событийного

Два способа считать одно и то же дают разные числа в отчёте. Временной: делим окно на минуты, каждая минута — «хорошая» или «плохая» по какому-то порогу; бюджет — 43,2 плохих минуты. Событийный: считаем запросы; бюджет — 0,1% от общего числа запросов за окно.

Разница проявляется, когда трафик неравномерен. Возьмём сервис со средним потоком 50 запросов в секунду, пиком 100 и ночным провалом 5.

Всего запросов за 30 дней:  50 * 2 592 000 с   = 129 600 000
Событийный бюджет (0,1%):   129 600 000 * 0,001 = 129 600 неуспешных запросов

Инцидент 30 минут в пик (100 rps):
  1800 с * 100 = 180 000 неуспешных  ->  139% месячного бюджета

Инцидент 30 минут ночью (5 rps):
  1800 с * 5   = 9 000 неуспешных    ->  6,9% месячного бюджета

Временной бюджет оба инцидента посчитает одинаково: по 30 минут, по 69% месячного бюджета каждый. Событийный различает их в двадцать раз.

Какой правильный? Тот, который ближе к пользовательской боли. Полчаса ночью задели тысячи людей, полчаса в пик — сотни тысяч; событийный бюджет это отражает, временной — нет. Поэтому для пользовательских API по умолчанию берут событийный.

Но у временного есть своя ниша: он совместим с языком контрактов (SLA почти всегда пишут в «минутах простоя»), работает для сервисов без явных запросов — фоновых обработчиков, стриминговых пайплайнов, — и не ломается при почти нулевом трафике, где у событийного знаменатель обращается в ноль. Практический компромисс: считать событийно, а в отчёты для бизнеса переводить в «эквивалентные минуты полного отказа», деля сожжённые запросы на текущий rps, — и честно писать, что это перевод, а не измерение.

Скорость сжигания, а не остаток

Остаток бюджета — плохой сигнал для дежурного. «Осталось 60%» ничего не говорит: может, всё спокойно, а может, за последние десять минут ушло 20% и через полчаса не останется ничего. Полезная величина — скорость сжигания (burn rate): во сколько раз быстрее равномерного расхода вы тратите бюджет прямо сейчас.

$$ B = \frac{1 - \text{SLI}}{1 - \text{SLO}} $$

Числитель — наблюдаемая доля ошибок за короткое окно (SLI берётся не за всё окно бюджета, а за последние минуты или часы). Знаменатель — доля ошибок, которую бюджет позволяет держать постоянно. $B = 1$ значит: тратим ровно с той скоростью, чтобы бюджета хватило точно до конца окна и ни минутой дольше. $B = 10$ — бюджета хватит на десятую часть окна.

Доля ошибок сейчас Burn rate при SLO 99,9% Полный бюджет (30 дней) сгорит за
0,1% 1 30 суток
0,5% 5 6 суток
1% 10 3 суток
1,44% 14,4 50 часов
5% 50 14,4 часа
100% (полный отказ) 1000 43,2 минуты

Последняя строка — та же таблица минут с другой стороны: при полном отказе бюджет 99,9% сгорает ровно за отведённые 43,2 минуты. Прямое следствие: будить человека надо не по «упало», а по «горит слишком быстро». Каноническая схема из SRE Workbook — несколько порогов, каждый из которых отвечает на вопрос «какую долю месячного бюджета мы потеряем, если так пойдёт дальше».

Длинное окно Короткое окно Burn rate Доля бюджета за длинное окно Реакция
1 час 5 мин 14,4 2% разбудить
6 часов 30 мин 6 5% разбудить
1 сутки 2 часа 3 10% тикет
3 суток 6 часов 1 10% тикет

Числа не с потолка, проверяются умножением на долю окна (месяц — 720 часов): 14,4 * 1/720 = 2%, 6 * 6/720 = 5%, 3 * 24/720 = 10%, 1 * 72/720 = 10%. Логика порогов: будить ночью оправдано, если иначе за час-два потеряем существенный кусок месячного бюджета; медленное сжигание тоже важно, но подождёт до утра, потому что за рабочий день катастрофы не случится.

Зачем короткое окно рядом с длинным? Чтобы алерт гас. Длинное окно инерционно: инцидент закончился, а часовое окно ещё 55 минут содержит плохие данные и держит условие истинным. Дополнительное условие по короткому окну снимает алерт почти сразу после восстановления.

Многооконный алерт: длинное и короткое окно

В конфигурации Prometheus это выглядит так:

groups:
  - name: slo-checkout-availability
    rules:
      # Доли ошибок за 5 минут и за час считаем заранее (recording rules),
      # чтобы алерт не пересчитывал тяжёлое выражение на каждом цикле.
      - record: job:slo_checkout:error_ratio_5m
        expr: sum(rate(http_requests_total{job="checkout",code=~"5.."}[5m]))
              / sum(rate(http_requests_total{job="checkout"}[5m]))
      - record: job:slo_checkout:error_ratio_1h
        expr: sum(rate(http_requests_total{job="checkout",code=~"5.."}[1h]))
              / sum(rate(http_requests_total{job="checkout"}[1h]))

      # SLO 99,9% -> допустимая доля ошибок 0,001; порог 14,4x -> 0,0144.
      - alert: CheckoutErrorBudgetFastBurn
        expr: job:slo_checkout:error_ratio_1h > 0.0144
              and job:slo_checkout:error_ratio_5m > 0.0144
        for: 2m
        labels: { severity: page }
        annotations:
          summary: "Бюджет ошибок checkout горит со скоростью 14,4x"
          description: "При такой скорости месячный бюджет кончится за ~50 часов."

Подробнее про устройство алертов и про то, как не утопить дежурного, — в главе «Алерты»; про то, откуда берутся сами метрики, — в «Мониторинге».

Считаем в коде

Вся арифметика бюджета — несколько формул без циклов, $O(1)$ по времени и памяти. Держать её стоит в одном месте, чтобы дашборд, алерт и отчёт считали одинаково.

from dataclasses import dataclass

@dataclass(frozen=True)
class ErrorBudget:
    """Бюджет ошибок одного SLO. Все методы — O(1) по времени и памяти."""
    slo: float           # доля успеха, например 0.999
    window_days: int     # длина окна, обычно 28 или 30

    @property
    def allowance(self) -> float:          # допустимая доля неуспеха
        return 1.0 - self.slo

    @property
    def allowed_minutes(self) -> float:    # временной бюджет
        return self.allowance * self.window_days * 24 * 60

    def allowed_events(self, total: int) -> float:   # событийный бюджет
        return self.allowance * total

    def burn_rate(self, error_ratio: float) -> float:
        """Во сколько раз быстрее равномерной нормы тратится бюджет сейчас."""
        return error_ratio / self.allowance

    def hours_left(self, error_ratio: float, spent: float = 0.0) -> float:
        """Через сколько часов бюджет закончится при той же скорости."""
        rate = self.burn_rate(error_ratio)
        return float("inf") if rate <= 0 else \
            self.window_days * 24.0 * max(0.0, 1.0 - spent) / rate


b = ErrorBudget(slo=0.999, window_days=30)
print(round(b.allowed_minutes, 1))              # 43.2
print(round(b.burn_rate(0.0144), 1))            # 14.4
print(round(b.hours_left(0.0144), 1))           # 50.0
print(round(b.hours_left(0.0144, 0.6), 1))      # 20.0 — бюджет уже потрачен на 60%

Последняя строка — деталь, которую часто упускают. Порог «14,4x» рассчитан на полный бюджет; если к середине месяца сожжено 60%, та же скорость оставляет не 50 часов, а 20. Некоторые команды делают пороги адаптивными — снижают их по мере расхода. Математически честнее, операционно хуже: дежурный перестаёт понимать, почему сегодня будят от того, от чего вчера не будили. Разумный компромисс — фиксированные пороги для страниц, остаток бюджета на дашборде и в еженедельном обзоре.

Хватит ли у вас данных, чтобы это измерить

Вопрос, который почти никогда не задают, назначая SLO: отличима ли ваша цель от шума на вашем объёме трафика? Если за месяц проходит 10 000 запросов, бюджет при 99,99% — один запрос. Один. Два неудачных — и SLO нарушено вдвое: это измерение не надёжности, а случайности.

Формализуем. Чтобы оценить малую вероятность $p$ с относительной точностью $\varepsilon$ при доверии 95%, нужно примерно

$$ n \approx \frac{z^2 (1 - p)}{\varepsilon^2 \, p}, \quad z = 1{,}96 $$

Цель $p = 1 - \text{SLO}$ События при точности ±50% При точности ±10%
99,9% 0,001 ~15 тыс. ~384 тыс.
99,99% 0,0001 ~154 тыс. ~3,8 млн
99,999% 0,00001 ~1,5 млн ~38 млн

Читается так: если у вас меньше полутора миллионов запросов в окне, фраза «мы держим 99,999%» не является утверждением о системе — вы просто не набрали достаточно наблюдений, чтобы отличить эту гипотезу от «99,99%».

Есть и вырожденный случай: ни одной ошибки за окно. Кажется, что это доказывает высокую надёжность, но правило трёх (Hanley & Lippman-Hand, JAMA, 1983) говорит: если в $n$ испытаниях не было ни одного отказа, верхняя 95%-я граница вероятности отказа примерно $3/n$. Ноль ошибок на 30 000 запросов даёт p <= 0,0001 — «не хуже 99,99%»; ноль ошибок на 3 000 запросов — только «не хуже 99,9%». То есть «месяц без единой ошибки» на трёх тысячах запросов не даёт права заявить даже четвёртую девятку. Подробнее про доверительные интервалы и оценку редких событий — в главе «Вероятность и статистика».

def min_events(target_slo: float, rel_precision: float = 0.5, z: float = 1.96) -> float:
    """Минимальный объём событий, чтобы говорить о цели всерьёз. O(1)."""
    p = 1.0 - target_slo
    return z * z * (1.0 - p) / (rel_precision ** 2 * p)

for slo in (0.999, 0.9999, 0.99999):
    print(slo, round(min_events(slo)))   # 15351 / 153648 / 1536624

Практический вывод для небольших сервисов: берите менее амбициозные SLO и более длинные окна. 99,5% на квартальном окне — измеримое утверждение. 99,99% на недельном при тысяче запросов — самообман, оформленный в виде дашборда.

Что бюджет меняет в решениях

До сих пор была арифметика. Теперь — единственная вещь, ради которой она нужна: бюджет ошибок превращает спор о ценностях в проверку условия. Договорённость записывается заранее, до того как что-то сломалось, и называется политикой бюджета ошибок. Минимальная рабочая версия:

# error-budget-policy.yaml — согласовано с продуктом, действует с 2026-01-01
service: checkout-api
slo: { objective: 0.999, window: 30d, budget_minutes_equiv: 43.2 }

states:
  - name: healthy                          # остаток бюджета > 50%
    allowed: [обычные релизы, рискованные эксперименты, работы с зависимостями]
  - name: caution                          # остаток 20–50%
    allowed: [релизы только за канареечной выкаткой с автооткатом]
    required: [в каждом стендапе назвать причину расхода за прошлые сутки]
  - name: depleted                         # остаток < 20%
    allowed: [исправления надёжности и безопасности, релизы снижающие расход]
    blocked: [выкатка новой функциональности]
    required:
      - одна инженерная неделя в спринт уходит на устранение причин
      - письменное решение владельца продукта, если блокировку снимают

exceptions:                                # без этого пункта политика — ловушка
  - "Релиз, устраняющий текущий инцидент, не блокируется никогда."
  - "Требования безопасности имеют приоритет над состоянием бюджета."
escalation: владелец сервиса -> владелец продукта -> директор по инженерии

Тот же документ как машина состояний:

Решение о конкретном релизе:

Механику канареечных выкаток и флагов разбирает глава «Безопасные релизы»; общий контекст стратегий выката — в DevOps. А вот как выглядит сам разговор, когда бюджет есть:

Сравните с версией без бюджета: «нам надо выкатить, бизнес ждёт» против «у нас всё падает, дайте починить» — разговор без критерия завершения, который выигрывает тот, у кого выше должность.

Чего бюджет не делает

Заморозка релизов — тупой инструмент. Полный стоп поставки часто ухудшает ситуацию: изменения копятся, следующий релиз становится больше и рискованнее, а «размер изменения» — один из сильнейших предикторов инцидента. Поэтому в политике выше блокируется не «любой релиз», а «новая функциональность», и всегда есть исключение для фиксов. Ещё лучше — не блокировать, а менять режим: уменьшать размер выкаток, включать обязательную канарейку, повышать долю ручной проверки.

Бюджет не измеряет тяжесть. Тридцать минут деградации поиска и тридцать минут потери платежей одинаковы для формулы и несопоставимы для бизнеса. Лечится это не усложнением формулы, а несколькими SLO на разные пользовательские сценарии — с раздельными бюджетами. Складывать их нельзя: 50% остатка у поиска и 5% у платежей не дают «27,5% в среднем», они дают «платежи в красной зоне».

Неизрасходованный бюджет — тоже сигнал. Если три месяца подряд остаток не опускается ниже 90%, возможны два объяснения: SLO слишком мягкое (тогда его надо ужесточить) или вы вкладываете в надёжность больше, чем нужно, и недодаёте скорости. Второе — реальные упущенные возможности, просто их никто не считает, потому что за них не будят ночью. Бюджет, который никогда не тратится, — это не достижение, а неиспользованный ресурс.

Цена: где инженерное упирается в организационное

Надёжность стоит денег, времени и людей. Разговор о бюджете ошибок без разговора о цене — половина разговора.

Избыточность

Интуиция говорит: два экземпляра вместо одного — и отказ становится квадратом. Для независимых отказов так и есть: при недоступности $u = 1%$ пара даёт $u^2 = 0{,}01%$, то есть 99,99% и 4,32 минуты в месяц. Проблема в слове «независимых»: всегда есть общая причина — один и тот же баг в одном и том же образе, одна зона доступности, один истёкший сертификат, одна выкатка конфига. Если доля общих причин равна $c$, оценка становится

$$ U = c \cdot u + \left((1 - c) \cdot u\right)^2 $$

При $c = 0{,}2$ получаем U = 0,2 * 0,01 + (0,8 * 0,01)^2 = 0,002 + 0,000064 = 0,002064, то есть 99,794% и 89,2 минуты в месяц вместо обещанных четырёх — в двадцать раз хуже наивной оценки, и это ещё оптимистично.

Отсюда следствие: деньги, потраченные на второй экземпляр, окупаются только после того, как устранены общие причины отказа. Разные зоны, разные версии на канарейке, раздельные пути выката конфигурации, независимые сертификаты. Иначе вы платите за избыточность, а получаете корреляцию. Классификация отказов — в главе о моделях отказов, механика каскадов — в «Как ломаются распределённые системы».

Деньги

Посчитаем маржинальную девятку. SaaS с выручкой 2 000 000 USD в месяц, инфраструктура — 150 000 USD в месяц.

Прямые потери выручки (линейное приближение):
  при 99,9%    0,001 * 2 000 000 = 2 000 USD/мес
  при 99,99%   0,0001 * 2 000 000 = 200 USD/мес
  экономия перехода 99,9% -> 99,99%:  1 800 USD/мес

Цена перехода:
  вторая активная зона/регион (+60% инфраструктуры)   ~90 000 USD/мес
  два инженера для покрытия дежурств                 ~30 000 USD/мес
  итого ~120 000 USD/мес   ->   отношение 67 : 1

Даже умножив прямые потери на десять — учтя отток, нагрузку на поддержку, репутацию, — получим 18 000 против 120 000. Всё ещё в убыток. Из этого не следует «не вкладывайтесь в надёжность»; следует, что маржинальная девятка почти никогда не окупается прямыми потерями выручки. Её оплачивают другие вещи: неустойка в корпоративном контракте, требование регулятора, риск единичной катастрофы (не 43 минуты по чуть-чуть, а восемь часов подряд с попаданием в новости), чужой due diligence. Нет ни одного такого плательщика — вы, скорее всего, покупаете девятку из тщеславия. Про неустойки полезна глава «Контракты», про экономику решений — «Юнит-экономика».

Люди

Самая дорогая и хуже всего учитываемая статья. Ротация из 6 человек по неделе даёт 52 / 6 = 8,7 недели дежурства в год на человека; при трёх срабатываниях за неделю это 3 * 8,7 = 26 пробуждений в год, из них ночью — около десяти.

Десять прерванных ночей звучит терпимо, но они распределены неравномерно: одна тяжёлая неделя может дать восемь подряд. Прерывание сна даёт измеримое снижение внимания и скорости реакции на следующий день — то есть дежурство напрямую уменьшает пропускную способность команды днём, и этот убыток нигде не отражается, потому что «дежурство» и «разработка» лежат в разных строках плана. Со стороны руководителя это разобрано в главе «Дежурства и инциденты», со стороны инженера — в «Дежурстве».

Отдельно — алерты без действия. Механизм тут не про усталость как эмоцию, а про статистику: если большая часть срабатываний не требует действий, оптимальная стратегия дежурного — задерживать реакцию (посмотреть позже, дождаться второго сигнала). Задержка растёт, и она растёт для всех алертов, включая настоящие. В медицине этот эффект измерен напрямую: в исследовании мониторов в отделениях интенсивной терапии подавляющее большинство сигналов оказались клинически незначимыми (Drew et al., PLOS ONE, 2014), и следствием стало снижение реакции на значимые. Инженерная система от медицинской здесь не отличается ничем.

Отсюда прямая связь с бюджетом: алерт имеет право будить человека только тогда, когда бездействие сожжёт значимую долю бюджета. Всё остальное — тикет, дашборд, недельный обзор. Это и есть содержательный критерий, которого обычно не хватает при разборе «почему у нас 200 алертов и никто на них не смотрит».

Приоритизация вложений

Когда бюджет исчерпан и надо решать, куда девать инженерное время, работает простое сравнение «цена вмешательства против возвращаемого бюджета»:

Левый верхний угол почти всегда занимают дешёвые изменения в поведении при отказе: таймауты, ограничение и джиттер ретраев, размыкатели, деградация вместо отказа. Они возвращают бюджет быстрее, чем любое резервирование, потому что бьют по каскадам — а каскад и есть механизм, превращающий десятисекундный сбой в получасовой инцидент. Каскадный отказ — это усиливающая петля обратной связи в чистом виде: ошибка порождает ретраи, ретраи порождают перегрузку, перегрузка порождает ошибки. Подробно — в главе «Деградация вместо отказа».

Почему бюджет ломается: обратная связь и данные

Бюджет ошибок — измерительный контур, и губит его чаще всего не техника: если по показаниям прибора наказывают, показания перестают быть данными. Механизм разворачивается по шагам, и ни на одном никто не делает ничего злонамеренного:

  1. Руководство объявляет: исчерпанный бюджет — плохой показатель работы команды.
  2. Спорный инцидент («лаг был, но пользователи вроде не жаловались») классифицируют как «не влиял на SLI».
  3. Определение «хорошего запроса» аккуратно уточняют: таймауты клиента исключаем, ведь это не наша ошибка.
  4. Инциденты длительностью меньше пяти минут перестают заводить, потому что «это же не инцидент».
  5. Через квартал бюджет всегда зелёный, а пользователи жалуются ровно так же, как раньше.

Прибор не сломался — его отключили от реальности. И заметьте: команда всё это время вела себя рационально. Именно поэтому постмортемы без обвинения — не проявление доброты, а требование к качеству данных: при поиске виноватого поток информации об отказах иссякает у источника, и первым исчезает как раз то, что нужнее всего, — сообщения о «мелочах», которые ещё не стали инцидентами. Механику подробно разбирает глава «Постмортемы».

Практические предохранители:

  • Определение SLI меняется только через явную процедуру — с датой, автором, причиной и пересчётом исторических данных. Тихая правка регулярного выражения в дашборде должна быть так же неприемлема, как тихая правка бухгалтерской проводки.
  • Бюджет не входит в оценку сотрудников. Ни в премии, ни в перформанс-ревью. Как только войдёт — см. пять шагов выше.
  • Разные люди владеют определением и расходом. Тот, кто может изменить SLI, не должен быть тем, кого спрашивают за расход бюджета.
  • Сверка с реальностью раз в квартал. Совпадает ли зелёный бюджет с обращениями в поддержку? Если нет — сломан SLI, а не поддержка.

Это тот же архетип, что и в главе о петлях обратной связи: контур измерения и контур управления, замкнутые на одних и тех же людях, порождают дрейф цели вместо изменения поведения.

Что переносится от Google, а что нет

Понятие бюджета ошибок пришло из книги Google SRE, и это хорошая книга — но её главный организационный механизм переносится плохо. У Google бюджет работает потому, что за ним стоит рычаг: команда SRE может вернуть пейджер — снять с себя поддержку сервиса и передать её обратно команде разработки, если та систематически выпускает ненадёжный код. Этот рычаг существует, потому что SRE — отдельная организация со своим бюджетом, своим наймом и правом отказаться от работы. В компании, где «SRE» — это два человека внутри той же команды, которая пишет фичи, возвращать пейджер некому и некуда. Политика бюджета там держится не на структурном рычаге, а на договорённости, которую можно нарушить без последствий.

Переносится практически без потерь: вся арифметика этой главы (она не зависит от масштаба); многооконные алерты по скорости сжигания вместо алертов по порогу метрики; заранее записанная политика — договориться о правилах до инцидента дешевле, чем во время; принцип «алерт только тогда, когда бездействие дорого стоит»; разделение SLO по пользовательским сценариям вместо одного числа на весь продукт.

Не переносится: отдельная организация SRE с правом отказа от поддержки; объёмы трафика, на которых четвёртая девятка вообще измерима; собственная инфраструктура, где можно менять поведение сети под нагрузкой; десятки людей в ротации, за счёт которых дежурство не выжигает; допущение, что есть время на «эталонный» процесс — в команде из шести человек постмортем на двадцать страниц не напишут никогда, и требовать его значит не получить вообще никакого.

Минимально жизнеспособная версия для маленькой команды: одно-два SLO на ключевые сценарии, скользящее окно 30 дней; два порога алертов (быстрый будит, медленный заводит тикет); политика на одну страницу с обязательным пунктом об исключениях; пятнадцать минут на еженедельном обзоре, где смотрят остаток и называют причину расхода. Этого достаточно, чтобы разговор о релизе перестал быть спором о характерах. Развёрнуто — в главе «SRE в небольшой команде».

Типичные ошибки

  • SLO без окна. «99,9%» без указания периода не проверяемо и не обсуждаемо.
  • Календарный месяц вместо скользящего окна. Первого числа бюджет «обнуляется», и появляется соблазн выкатывать рискованное в конце месяца. Скользящее окно убирает обрыв.
  • Цель, скопированная у соседа. 99,99% выбрано потому, что «у всех так», а не потому, что кто-то посчитал MTTR и объём трафика.
  • Один бюджет на весь продукт. Логин, поиск и оплата смешиваются в одно среднее число, которое не говорит ни о чём.
  • Алерт на сам факт исчерпания бюджета. Это не срочно и не действенно: бюджет уже потрачен. Срочно — высокая скорость сжигания.
  • Пороги burn rate, не пересчитанные под своё окно. Числа 14,4 и 6 выведены для 30-дневного окна; для 7-дневного они другие.
  • Бюджет как инструмент отчётности перед начальством. Гарантированный путь к тому, что через квартал он всегда зелёный.
  • Заморозка вообще всего. Изменения копятся, следующий релиз крупнее и опаснее.
  • Игнорирование неизрасходованного бюджета. Постоянные 95% остатка — сигнал пересмотреть цель, а не повод для гордости.
  • Расчёт избыточности без общих причин отказа. Обещанные $u^2$ на практике оказываются на порядок хуже.

Мини-итог

Бюджет ошибок — это $1 - \text{SLO}$, выраженное в минутах или запросах, привязанное к окну и пересчитываемое непрерывно. Три числа наизусть: 99,9% — 43 минуты в месяц, 99,99% — 4,3 минуты, 99,999% — 26 секунд, то есть отсутствие человека в контуре реакции.

Смотреть надо не на остаток, а на скорость сжигания: она отвечает на вопрос «когда закончится», остаток — только на «сколько было». Многооконные пороги переводят эту скорость в решение «будить или тикет». Считать бюджет имеет смысл лишь на том объёме трафика, где его можно измерить: полтора миллиона событий в окне для разговора о пяти девятках, пятнадцать тысяч — о трёх.

И главное: бюджет ценен не как метрика, а как заранее согласованное правило, которое превращает спор «выкатывать или чинить» в проверку условия. Правило работает ровно до тех пор, пока по нему не начинают наказывать, — после этого оно превращается в упражнение по подгонке определений, и данные о реальных отказах перестают поступать.

Источники

  • Google. Site Reliability Engineering, глава «Embracing Risk» — sre.google/sre-book/embracing-risk.
  • Google. The Site Reliability Workbook: Implementing SLOs, Alerting on SLOs, Error Budget Policy.
  • Alex Hidalgo. Implementing Service Level Objectives (O’Reilly, 2020) — oreilly.com. Наиболее подробный разбор SLO вне гугловского контекста.
  • Hanley J., Lippman-Hand A. If Nothing Goes Wrong, Is Everything All Right?JAMA, 1983. Источник «правила трёх».
  • Drew B. et al. Insights into the Problem of Alarm Fatigue with Physiologic Monitor DevicesPLOS ONE, 2014.
  • Richard Cook. How Complex Systems Failhow.complexsystems.fail.
  • AWS Builders’ Library — aws.amazon.com/builders-library, в частности материалы о таймаутах, ретраях и ограничении нагрузки.
  • Публичные SLA как источник реальных цифр: Amazon EC2, Amazon S3, Google Compute Engine.
  • OpenSLO — открытая спецификация описания SLO: openslo.com. Инструменты генерации правил: Sloth, Pyrra.
  • Документация Prometheus по recording rules — prometheus.io.

Что дальше

Мы всю главу оперировали числами так, будто они просто есть: доля неуспешных запросов за час, скорость сжигания за пять минут, остаток бюджета на дашборде. На практике это самая хрупкая часть конструкции. Метрику надо собрать так, чтобы она пережила отказ самого сервиса; отличить «сервис вернул 500» от «сервис не ответил вовсе»; понять, почему доля ошибок выросла, — а для этого одних метрик мало, нужны логи и трейсы. И решить, что из этого хранить, потому что хранение стоит денег, иногда сопоставимых с самой инфраструктурой.

Дальше — Мониторинг: метрики, логи, трейсы и что из этого когда.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов