SRE и надёжность SLI и SLO: как выбрать показатель, который что-то значит
0%

SLI и SLO: как выбрать показатель, который что-то значит

SLI и SLO: как выбрать показатель, который что-то значит

В четверг вечером поддержка получила сорок обращений «не могу оплатить». Дежурный открыл дашборд: все панели зелёные. Аптайм 100 %, CPU в норме, память в норме, /health отвечает 200 уже 94 дня подряд. Через час выяснилось, что платёжный шлюз отвечает 200 с телом {"status":"pending"}, которое фронтенд показывает как бесконечный спиннер. Формально сервис работал. Фактически — нет.

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

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

Три буквы, которые постоянно путают

Термин Что это Кто адресат Что бывает при нарушении
SLI (Service Level Indicator) измеряемая величина: доля хороших событий среди всех значимых инженеры ничего, это просто число
SLO (Service Level Objective) цель по SLI на конкретном окне: «≥ 99,9 % за 28 дней» команда и владелец продукта внутреннее решение: тормозим изменения, чиним
SLA (Service Level Agreement) договор с клиентом, где к цели привязаны деньги юристы и продажи компенсация, штраф, скидка на счёт

Порядок важен: SLI — измерение, SLO — обещание себе, SLA — обещание другим. И SLA всегда слабее SLO: если вы продали клиенту 99,9 %, внутри держите 99,95 %, иначе первое же нарушение сразу превращается в деньги, без шанса заметить сползание. Практическая проверка, что перед вами SLA, а не SLO: спросите, кто и как выплачивает компенсацию. Нет ответа — это SLO, как бы его ни называли в презентации.

Ещё частая путаница — SLA облачных провайдеров. AWS обещает по EC2 SLA 99,99 % на регион при условии, что вы развернулись минимум в двух зонах доступности, а компенсация — скидка на счёт, а не возмещение вашего ущерба. Это не то же самое, что «мои виртуалки будут доступны 99,99 %»: одна инстанс-группа в одной зоне такой гарантии не имеет.

SLI — это доля, а не среднее

Каноническая форма SLI ровно одна:

SLI = (число хороших событий) / (число значимых событий) × 100 %

«Хорошее» и «значимое» — это два определения, которые надо написать словами и защитить. Всё остальное — детали реализации.

Почему именно доля, а не среднее? Потому что среднее скрывает ровно то, что вас интересует. Возьмите тысячу запросов: 990 отвечают за 50 мс, 10 висят по 30 секунд. Среднее время ответа — (990·50 + 10·30000) / 1000 = 349 мс. Выглядит приемлемо. При этом каждый сотый пользователь ждал полминуты и ушёл. Доля запросов быстрее 300 мс — 99,0 %, и это число сразу показывает проблему. Подробный разбор, почему средние по латентности бесполезны, есть в главе про измерение производительности.

Второе следствие «доли»: числитель и знаменатель надо определить явно и жёстко. Самая частая ошибка живёт в знаменателе.

  • Health-чеки. Если Kubernetes долбит /healthz каждые 5 секунд, а это 30 % всего трафика и всегда 200, SLI разбавляется. Реальная доля ошибок пользователей 0,3 % превращается в измеренные 0,3 % × 0,7 = 0,21 %, то есть SLI показывает 99,79 % вместо 99,7 %. Разница как раз в масштабе бюджета — то есть в масштабе решений.
  • Боты и сканеры. Их запросы часто дают 404 и 400, портя SLI в другую сторону.
  • Ретраи. Один пользовательский заказ после трёх ретраев — это три сетевых запроса, из которых два неудачные. Если считать по сетевым, SLI 66 %, хотя пользователь получил результат. Если считать по логическим операциям — 100 %, хотя пользователь ждал втрое дольше. Оба ответа неверные; правильный — считать пользовательскую операцию хорошей, если она завершилась в срок с учётом ретраев.
  • 4xx. Обычно исключают из числителя «плохих»: клиент прислал мусор — сервис не виноват. Но 429 (rate limit) — это ваше решение отказать, а 400 может быть следствием того, что вы неудачно поменяли схему. Правило: 4xx исключаем по умолчанию, 429 считаем плохим, всплеск 400 после релиза расследуем как инцидент.

Откуда берётся показатель: критические пользовательские пути

Не начинайте с метрик, которые у вас уже есть. Начинайте с вопроса «что человек пытается сделать» — это critical user journey, критический пользовательский путь. Для интернет-магазина их обычно три-четыре: найти товар, добавить в корзину, оплатить, посмотреть статус заказа. Для каждого формулируется, что для пользователя означает «сработало», и это переводится в тип показателя.

Полный набор типов, который покрывает практически всё:

Тип SLI Что считаем Где особенно уместен
Доступность доля запросов без ошибки сервера синхронные API, веб
Латентность доля запросов быстрее порога всё интерактивное
Корректность доля результатов, прошедших сверку биллинг, отчёты, ETL
Свежесть доля чтений, где данные моложе N кэши, реплики, витрины
Полнота доля входных записей, доехавших до выхода пайплайны, очереди
Пропускная способность доля времени, когда система тянет заявленный поток батчи, стриминг
Долговечность доля объектов, которые не потерялись хранилища, бэкапы

Для конвейеров данных естественная пара — свежесть и полнота: «97 % партиций готовы к 06:00» и «доля потерянных записей ниже 0,01 %». Доступность там почти ничего не значит.

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

Где мерить: точка наблюдения решает больше, чем формула

Одна и та же формула, посчитанная в разных точках, даёт разные числа — и разные слепые зоны.

Точки наблюдения на пути запроса и их слепые зоны

Разница не теоретическая. Если сервис упал целиком, метрика внутри сервиса просто перестаёт поступать — и наивный запрос вида «доля 5xx» покажет отсутствие данных, а не 0 %. Балансировщик в этот момент честно запишет 502 на каждый запрос. Отсюда базовое правило: считайте SLI доступности как можно ближе к пользователю, но там, где объём данных достаточен. Практический компромисс:

  1. Основной SLI — по логам или метрикам балансировщика/ingress. Видит отказ сервиса, имеет полный объём трафика, дёшев (прокси и балансировка).
  2. Дополнительный — RUM из клиента. Не как SLO, а как контроль: если RUM систематически хуже серверного SLI на 0,5 %, у вас проблема на пути, который вы не мерите.
  3. Синтетика — для путей с малым трафиком и для проверки DNS/TLS. Ссылаться на неё в SLO опасно: сотня проб в час — это статистика, где один сбойный прогон даёт 1 % ошибок.

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

Событийный SLI против временного

Есть два способа считать одно и то же, и они дают разные числа. Событийный (request-based) — доля хороших запросов среди всех. Временной (time-based) — доля «хороших минут» среди всех минут, где минута считается хорошей, если доля ошибок в ней ниже порога.

Событийный и временной SLI на одном инциденте

На картинке — один и тот же четырёхминутный отказ, попавший в пик нагрузки. Событийный счёт даёт 63,6 %, временной — 80 %. Разница не в арифметической ошибке, а в том, что событийный счёт взвешивает по трафику: отказ в час пик стоит дороже, чем отказ ночью. Именно так это ощущает бизнес.

Когда что применять:

Событийный Временной
Плюс взвешен по реальному ущербу работает при нулевом трафике
Минус при малом трафике шумит отказ в пик неотличим от отказа ночью
Годится для API, веб, синхронные вызовы батчи, задания по расписанию, кластеры
Бюджет считается в плохих запросах плохих минутах

Смешивать нельзя: если SLO объявлен событийным, а бюджет вы потом считаете в минутах, числа не сойдутся, и первый же спор о релизе выродится в спор о методике.

Латентность: порог вместо перцентиля

Соблазн написать «p99 ≤ 300 мс» велик, но это плохой SLI, и по двум причинам.

Причина первая: перцентили не складываются. Инстанс A обработал 1000 запросов с p99 = 200 мс. Инстанс B обработал 10 запросов с p99 = 2000 мс. Среднее «p99» = 1100 мс — число, не соответствующее ничему в реальности. Настоящий p99 по всем 1010 запросам ближе к 250 мс: у B всего 10 запросов, из них медленных единицы, и на общем хвосте они почти не видны. Усреднять перцентили нельзя — ни по инстансам, ни по времени. Строго говоря, чтобы получить корректный перцентиль по объединению, нужны исходные наблюдения или совместимые гистограммы.

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

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

SLI = (запросов быстрее порога) / (всех значимых запросов)

SLO 1: 99,0 % запросов быстрее 300 мс   — комфорт большинства
SLO 2: 99,9 % запросов быстрее 2 с      — нет зависших

Это тот же смысл, что «p99 ≤ 300 мс», но выраженный счётчиком, который складывается по инстансам, времени и регионам простым суммированием.

Откуда берутся сами пороги? Не из текущего p99 — так вы заморозите статус-кво и объявите целью то, что уже есть. И не с потолка. Рабочий способ: посмотреть на распределение времени ответа против поведения (доля отказов от покупки, доля повторных нажатий), найти точку, после которой поведение портится, и поставить порог там. Если данных о поведении нет, отталкивайтесь от продуктовых требований: «человек не должен успеть подумать, что зависло» — это примерно секунда. Про сбор таких данных — метрики в продуктовом треке. И отдельная засада — coordinated omission: если нагрузочный клиент или инструментация ждут ответа перед следующим запросом, самые медленные ответы систематически не попадают в статистику, и картина получается радужнее реальности (доклад Гила Тене «How NOT to Measure Latency», практическая сторона — в главе о нагрузочном тестировании).

Арифметика девяток

Теперь считаем. Месяц берём как 30 суток = 30 × 24 × 60 = 43 200 минут. Год — 365 × 24 × 60 = 525 600 минут. Бюджет недоступности = (1 − цель) × длительность окна.

Цель Неделя (10 080 мин) Месяц (43 200 мин) Год (525 600 мин)
99 % 100,8 мин ≈ 1 ч 41 мин 432 мин = 7 ч 12 мин 5 256 мин ≈ 3 сут 15,6 ч
99,5 % 50,4 мин 216 мин = 3 ч 36 мин 2 628 мин ≈ 1 сут 19,8 ч
99,9 % 10,08 мин 43,2 мин = 43 мин 12 с 525,6 мин ≈ 8 ч 45 мин
99,95 % 5,04 мин 21,6 мин = 21 мин 36 с 262,8 мин ≈ 4 ч 23 мин
99,99 % 1,008 мин 4,32 мин = 4 мин 19 с 52,56 мин ≈ 52,6 мин
99,999 % 6,05 с 25,92 с 5,26 мин

Проверка одной строки, чтобы не верить на слово: (1 − 0,999) × 43 200 = 0,001 × 43 200 = 43,2 минуты. И для пяти девяток: (1 − 0,99999) × 43 200 = 0,00001 × 43 200 = 0,432 минуты = 25,92 секунды.

Для событийного SLI считается так же, только в запросах. Пусть сервис обрабатывает 10 000 000 запросов в месяц и SLO = 99,9 %:

допустимо плохих = (1 − 0,999) × 10 000 000 = 10 000 запросов

Десять тысяч звучит много. Но если в пике 200 rps и пятиминутная деградация с полным отказом, то 200 × 300 = 60 000 ошибок — шесть месячных бюджетов за пять минут. А та же пятиминутная деградация ночью при 10 rps даст 10 × 300 = 3 000 ошибок, то есть 30 % бюджета. Один и тот же инцидент, разница в двадцать раз. Это и есть причина, по которой канареечные выкатки катят ночью, а не в пятницу в 18:00 — см. безопасные релизы.

Связь с MTTR: чинить быстрее выгоднее, чем ломать реже

A = MTBF / (MTBF + MTTR)

отказы раз в 30 суток, чиним 30 мин: A = 43 200 / (43 200 + 30) = 0,999306 → 99,93 %
то же, но чиним 5 мин:               A = 43 200 / (43 200 +  5) = 0,999884 → 99,988 %

Почти четыре девятки без единого изменения в частоте отказов. Формально удвоение MTBF и уменьшение MTTR вдвое дают ровно одинаковый эффект — 2M/(2M+R) = M/(M+R/2). Но на практике вдвое сократить время восстановления (автоматический откат, готовый ранбук, переключение трафика) почти всегда дешевле, чем вдвое сократить частоту отказов: источники отказов разнородны и их много. Отсюда вывод: инвестиции в деградацию вместо отказа и в скорость отката окупаются лучше, чем ещё один слой тестов.

Почему пять девяток почти никому не нужны

99,999 % — это 25,92 секунды в месяц. Посчитаем, что в такой бюджет не помещается: перезапуск пода с прогревом JVM или пулов соединений — 20–60 секунд (один плановый рестарт съедает месячный бюджет); протухший DNS-кэш с TTL 60 секунд — 60 секунд; TCP-таймаут по умолчанию у многих клиентов — 30 секунд на одну неудачную попытку; выборы лидера в кворумной системе после падения узла — единицы-десятки секунд (консенсус).

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

Ключевой аргумент — человек. Посчитаем реалистичную цепочку реакции:

Этап Оптимистично
Алерт сработал (окно детекции) 1–2 мин
Доставка уведомления, дежурный проснулся 3–5 мин
Открыл ноутбук, зашёл в дашборд 3 мин
Понял, что происходит 5–10 мин
Запустил откат, откат доехал 3–5 мин
Итого 15–25 мин

Один такой инцидент — это пять-шесть месячных бюджетов при 99,99 % и полсотни при 99,999 %. Вывод, который не зависит от вашей квалификации: начиная примерно с 99,99 % человек в цикле восстановления невозможен арифметически. Не «нежелателен» — невозможен. Хотите четыре девятки — вы обязаны построить автоматическое обнаружение и автоматическое восстановление, а дежурный нужен для расследования постфактум, а не для тушения. Это конкретное инженерное требование, вытекающее из одного умножения.

Второй аргумент — наблюдаемость со стороны пользователя. Мобильная сеть даёт 99–99,5 % успешных соединений в хороших условиях. Домашний Wi-Fi и провайдер добавляют свои отказы. Если пользователь и так видит 1 % неудач из-за собственной сети, разница между вашими 99,99 % и 99,999 % для него неразличима: 0,01 % против 0,001 % на фоне 1 % шума. Вы платите за девятку, которую физически некому заметить.

Третий — цена. Переход 99,9 → 99,99 обычно означает мультизональное развёртывание, автоматический откат, дублирование хранилища, отдельную работу по устранению единых точек отказа. Порядок затрат — удвоение счёта за инфраструктуру плюс месяцы инженерного времени. Выигрыш: 43,2 − 4,32 = 38,88 минуты в месяц. Вопрос, который надо задать владельцу продукта до начала работ: «эти 39 минут в месяц стоят удвоения инфраструктурного бюджета?» Иногда да (торговля на бирже, телемедицина, платёжный процессинг). Чаще нет. Сравнение затрат подробно — в главе про стоимость облака.

Композиция: SLO зависимостей и предел избыточности

Ваш SLO не может быть выше того, что позволяют зависимости, — если только вы не научились работать без них.

Последовательная зависимость (отказ любого компонента = отказ пути): доступности перемножаются.

4 компонента по 99,99 %:
A = 0,9999^4 = 0,99960006 → 99,96 %
бюджет = (1 − 0,9996) × 43 200 ≈ 17,3 мин/мес

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

Избыточность (работает, пока жива хоть одна копия): при независимости отказов перемножаются недоступности. Но реплики не независимы — общий деплой, общая конфигурация, общий сертификат, общая база, общий баг. Введём долю c коррелированных отказов, валящих обе копии сразу:

2 реплики по 99,9 %, недоступность u = 0,001

идеал (c = 0):   A = 1 − u² = 0,999999      → 99,9999 %, бюджет 0,0043 мин/мес
реальность (c = 0,3):
  P(обе легли) = c·u + ((1 − c)·u)² = 0,0003 + (0,0007)² ≈ 0,00030049
  A ≈ 0,99969951                            → 99,97 %,   бюджет ≈ 13 мин/мес

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

Отсюда же ответ на частый вопрос: «нам нужно 99,99 %, а провайдер даёт 99,9 % — что делать?» Варианты ровно три: убрать зависимость с критического пути (кэш, очередь, деградация), продублировать её другим провайдером (дорого, и корреляция всё равно останется), либо снизить свою цель. Четвёртого нет, и «поговорить с провайдером» им не является.

Как выбрать целевое число

Плохой способ: собраться и решить, «сколько мы хотим». Хотят всегда 100 %. Рабочий способ — три шага.

  1. Измерить, как есть. Заведите SLI и смотрите на него месяц-два, ничего не обещая. Почти всегда реальность отличается от ожиданий, причём в обе стороны.
  2. Сопоставить с болью. Наложите на график обращения в поддержку, отказы от покупки, жалобы в чате. Уровень, на котором начинают жаловаться, — это и есть граница «пользователь заметил».
  3. Поставить цель чуть выше достигнутого, но ниже идеала. Фактически 99,7 %, жалобы начинаются ниже 99,5 % — цель 99,8 %, а не 99,99 %.

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

Полезная поправка — пересчитать цель на сессию, а не на запрос. Если типичная сессия это 20 запросов:

при SLI 99,9 %:  0,999^20  = 0,9802 → 1,98 % сессий задеты хотя бы одной ошибкой
при SLI 99,99 %: 0,9999^20 = 0,9980 → 0,20 % сессий

Два процента сессий с ошибкой при миллионе сессий в месяц — это двадцать тысяч задетых людей. Поэтому для длинных путей SLI лучше ставить на пользовательскую операцию целиком, а не на отдельный HTTP-запрос.

Нижний ряд — важнейшие метрики для расследования, но негодные как SLO: пользователю всё равно, какой у вас CPU. Правый нижний угол — ловушка: дорого собирать и всё равно не отражает пользовательский опыт.

Окно измерения

Окно — часть определения SLO, а не деталь. «99,9 %» без окна не значит ничего: за час это 3,6 секунды, за год — почти 9 часов.

Окно Плюс Минус
Скользящие 28 дней нет эффекта «сброса», всегда одинаковой длины (ровно 4 недели, недельная сезонность не искажает) инцидент «тянется» в статистике 28 дней
Календарный месяц совпадает с отчётностью и SLA разная длина, соблазн «дожечь бюджет в конце месяца»
Скользящие 7 дней быстро реагирует шумит, слишком чувствителен к одному инциденту
Квартал стабилен, годится при малом трафике реакция настолько медленная, что не влияет на решения

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

Спецификация SLO как артефакт

SLO, живущий в голове тимлида, — это не SLO. Он должен быть текстом в репозитории, из которого генерируются дашборд и алерты.

# slo.yaml — лежит рядом с кодом сервиса и ревьюится как код
service: checkout-api
owner: team-payments            # без владельца SLO не существует
reviewed: 2026-06-01            # дату следующего пересмотра ставим явно
slos:
  - name: availability-orders
    type: request-based         # событийный счёт, не временной
    objective: 99.9
    window: 28d
    measurement:
      point: "nginx-ingress, лог доступа"
      good: "код ответа не 5xx и не 429"
      valid: "все POST /orders, кроме health-чеков и User-Agent синтетики"
    blind_spots:
      - "падение самого ingress не видно — контролируем внешней пробой"
      - "проблемы DNS и TLS не входят в SLI"
  - name: latency-orders
    type: request-based
    objective: 99.0
    window: 28d
    measurement:
      point: "гистограмма http_request_duration_seconds на ingress"
      good: "длительность <= 0.3 с"
      valid: "тот же знаменатель, что у availability-orders"

Формализованные схемы для этого уже есть: OpenSLO как спецификация формата и Sloth как генератор правил Prometheus из такого YAML. Брать их не обязательно, писать спецификацию — обязательно.

Запросы для Prometheus. Событийная доступность за окно:

# доля не-5xx среди значимых запросов за 28 дней
sum(increase(http_requests_total{job="checkout-api", route="/orders", method="POST", code!~"5..|429"}[28d]))
  /
sum(increase(http_requests_total{job="checkout-api", route="/orders", method="POST"}[28d]))

Латентность через гистограмму. Здесь важно: le="0.3" должен существовать как граница бакета, иначе Prometheus считает по соседнему и вы получите неверное число. Бакеты подбирают под пороги SLO, а не наоборот (документация по гистограммам):

# доля запросов быстрее 300 мс за 28 дней
sum(increase(http_request_duration_seconds_bucket{job="checkout-api", route="/orders", le="0.3"}[28d]))
  /
sum(increase(http_request_duration_seconds_count{job="checkout-api", route="/orders"}[28d]))

Небольшой калькулятор, который удобно держать в тестах и в CI, чтобы числа в презентациях считал не человек:

"""Арифметика SLO: бюджет, композиция зависимостей, статистический шум."""
from dataclasses import dataclass
from math import sqrt

MINUTES_PER_DAY = 24 * 60

@dataclass(frozen=True)
class Slo:
    target: float       # доля, например 0.999
    window_days: int    # длина окна в сутках

    @property
    def budget_minutes(self) -> float:
        """Минуты недоступности, разрешённые целью за окно."""
        return (1 - self.target) * self.window_days * MINUTES_PER_DAY

    def budget_events(self, total_events: int) -> float:
        """Плохие события, разрешённые целью при известном объёме трафика."""
        return (1 - self.target) * total_events

def serial(components: list[float]) -> float:
    """Цепочка: отказ любого звена — отказ пути. O(n) по времени, O(1) по памяти."""
    result = 1.0
    for availability in components:
        result *= availability
    return result

def parallel(availability: float, replicas: int, correlated: float = 0.0) -> float:
    """Избыточность из n реплик; correlated — доля отказов по общей причине,
    которые валят все копии сразу. При correlated=0 это идеальная формула 1 - u**n."""
    u = 1 - availability
    return 1 - (correlated * u + ((1 - correlated) * u) ** replicas)

def observation_noise(target: float, events: int) -> float:
    """Стандартная ошибка наблюдаемой доли ошибок: sqrt(p*(1-p)/n).
    Показывает, различимы ли вообще две соседние цели при таком объёме."""
    p = 1 - target
    return sqrt(p * (1 - p) / events)

if __name__ == "__main__":
    slo = Slo(target=0.999, window_days=28)
    print(f"{slo.budget_minutes:.1f} мин за {slo.window_days} дней")              # 40.3
    print(f"плохих запросов из 10 млн: {slo.budget_events(10_000_000):.0f}")      # 10000
    print(f"цепочка 4 x 99.99%: {serial([0.9999] * 4):.6f}")                      # 0.999600
    print(f"2 реплики 99.9%, 30% общих причин: {parallel(0.999, 2, 0.3):.6f}")    # 0.999700
    print(f"шум при 5000 событий: +-{2 * observation_noise(0.999, 5000) * 100:.3f} п.п.")

Обратите внимание на первую строку вывода: бюджет за 28 дней — 40,3 минуты, а не 43,2, потому что окно короче месяца. Такие мелочи и ломают разговоры о бюджете, если считать в уме.

Малый трафик: когда SLO статистически бессмыслен

Тему обычно замалчивают, потому что в книгах примеры на миллиардах запросов. Пусть у вас 5 000 запросов в месяц — типично для внутреннего сервиса или B2B-продукта. SLO 99,9 % даёт бюджет 0,001 × 5000 = 5 плохих запросов. Пять. Неудачный деплой, отдававший 500 три минуты при 2 запросах в минуту, — это 6 ошибок, весь бюджет.

Хуже другое: при таком объёме наблюдаемая доля ошибок сама по себе случайная величина.

se = sqrt(p·(1 − p)/n) = sqrt(0,001 × 0,999 / 5000) = sqrt(1,998e−7) ≈ 0,000447 = 0,045 %

Два стандартных отклонения — 0,09 процентного пункта: при истинной доступности ровно 99,9 % вы будете наблюдать значения примерно от 99,81 % до 99,99 % просто из-за случайности. Отличить 99,9 % от 99,95 % при таком трафике невозможно в принципе, сколько дашбордов ни рисуй.

Что делать: взять окно длиннее (90 дней — шум падает как 1/sqrt(n)); перейти на временной SLI («доля пятиминуток, в которых сервис отвечал»); снизить цель до различимой (99 % при 5 000 запросов — это бюджет в 50 ошибок, уже измеримо); либо честно признать, что SLO здесь не работает, и вести список известных проблем плюс алерты по симптомам. Последнее — нормальный ответ, а не поражение.

Цена: за каждым SLO стоит человек, деньги и организационное решение

Это раздел, который чаще всего выкидывают, и зря — именно здесь SLO ломается на практике.

Дежурство. SLO 99,9 % с круглосуточным окном означает, что ночной инцидент должен быть починен за минуты. Считаем альтернативу: отказ в 02:00, обнаруженный в 09:00, — это 420 минут, почти десять месячных бюджетов за одну ночь. Значит, круглосуточный SLO 99,9 % требует круглосуточного дежурства. Круглосуточное дежурство с приемлемой нагрузкой требует минимум пяти-шести человек в ротации — иначе каждый дежурит слишком часто и выгорает. Это не метафора: ночные пробуждения ломают сон, накопленный недосып бьёт по качеству решений и по здоровью, и люди уходят. Механика и цена подробно — в главе о дежурстве, взгляд руководителя — в engineering-leadership.

Отсюда вполне легитимный выход: считать SLO по рабочему окну. Если пользователи — бухгалтеры, работающие с 9 до 19 по будням, знаменатель должен содержать только эти запросы. Тогда «99,9 % в рабочее время» — честная и достижимая цель без ночных дежурств. Это не жульничество, это соответствие показателя реальности. Жульничество — это обещать 99,9 % круглосуточно, не имея ночной ротации.

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

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

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

И ещё одно организационное: SLO нельзя делать личным KPI. Как только выполнение SLO влияет на премию, включается закон Гудхарта: показатель, ставший целью, перестаёт быть хорошим показателем. Практически это выглядит так — ошибки начинают возвращаться с кодом 200 и телом {"error": ...}, «плановые работы» исключаются из знаменателя задним числом, инциденты переклассифицируются в «деградацию». Данные портятся, и вы теряете единственный инструмент, который у вас был. Тот же механизм подробно разобран для постмортемов в главе 08: при поиске виноватого перестают поступать данные, а без данных нельзя чинить.

Жизненный цикл SLO

SLO — не константа. Продукт меняется, пользователи меняются, инфраструктура меняется.

Ключевой переход — observe → draft. Если за два месяца наблюдений SLI ни разу не просел во время реальных жалоб, показатель выбран неверно, и его надо менять, а не объявлять целью. Именно эту точку чаще всего пропускают: цель объявляют сразу, полгода спорят о её нарушениях и только потом выясняют, что мерили не то. Пересматривать имеет смысл раз в квартал и обязательно после крупного инцидента, отвечая на четыре вопроса: просел ли SLI, когда пользователям было плохо; срабатывал ли он, когда всё было хорошо; изменился ли профиль нагрузки; появились ли пути, которых нет в наборе.

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

  1. SLI по среднему — среднее прячет хвост, а хвост и есть недовольные пользователи. SLI по пингу /health — проверяет, что процесс жив, а не что он делает работу; история из начала главы ровно про это.
  2. Цель 100 % или «максимально высокая» — механизм ломается в первый день и дальше игнорируется. Двадцать SLO — ни одно не влияет на решения; два-четыре на сервис.
  3. Грязный знаменатель — health-чеки, боты, синтетика и внутренние вызовы разбавляют SLI и делают его нечувствительным.
  4. Перцентиль вместо доли — не складывается, не даёт бюджета, провоцирует усреднение перцентилей. Смешивание событийного и временного счёта в одном разговоре — то же самое, только про бюджет.
  5. Забыли окно — «99,9 %» без окна не цель, а лозунг. SLO без владельца — никто не отвечает, значит, никто и не смотрит.
  6. SLO выше, чем позволяют зависимости — обещание, арифметически невыполнимое. SLA = SLO — ноль запаса, первая же просадка сразу стоит денег.
  7. SLO в премии — данные начинают подстраиваться под цель.

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

Книги Site Reliability Engineering и The Site Reliability Workbook — лучший бесплатный материал по теме, и главы про SLO там читать стоит. Но Google описывает свою практику при своём масштабе и своих ресурсах, и часть этого не переносится вообще никуда.

Переносится почти всем Не переносится без оговорок
Разделение SLI / SLO / SLA отдельная команда SRE с правом вето на релиз
Формула «хорошие / значимые события» правило «не более 50 % времени на рутину»
Выбор показателя по пользовательскому пути избыточность уровня N+2 в нескольких регионах
Принцип «цель не 100 %» масштаб, при котором статистика работает на любом окне
Бюджет ошибок как язык разговора с продуктом собственные балансировщики, планировщик, сеть
Явная спецификация SLO в репозитории сотни постмортемов в год как источник данных

Главное несовпадение — статистическое: все примеры в книгах предполагают поток запросов, при котором доля ошибок измеряется устойчиво на любом окне, а у команды с 5 000 запросов в месяц те же формулы дают шум. Второе — организационное: у Google есть люди, чья работа держать надёжность, и институциональное право остановить релиз. В команде из шести человек, где надёжностью занимается тот, кто последним трогал прод, механизм бюджета работает ровно настолько, насколько команда сама решила его уважать.

Что делать такой команде: взять один-два SLO на главный путь, поставить достижимую цель, договориться, что происходит при её нарушении, и не пытаться воспроизвести остальное (развёрнуто — в заключительной главе трека). Книга не про Google-масштаб: Alex Hidalgo, «Implementing Service Level Objectives», O’Reilly, 2020 — там много про то, как договариваться и как считать при небольшом трафике.

Мини-итог

  • SLI — доля хороших событий среди значимых. Не среднее, не «аптайм», не перцентиль. Показатель выбирается от пользовательского пути, а не от того, что уже собирается в Prometheus.
  • Точка измерения определяет слепые зоны, и их надо записать в спецификации явно. Латентность выражается порогом («доля быстрее 300 мс»): перцентили не складываются и не дают бюджета.
  • 99,9 % = 43,2 мин/мес, 99,99 % = 4,32 мин/мес, 99,999 % = 25,9 с/мес. Начиная с четырёх девяток человек в цикле восстановления невозможен арифметически.
  • Последовательные зависимости перемножаются, избыточность упирается в долю общих причин. Считайте до того, как обещать.
  • Цель выбирается по измеренной реальности и по границе, где начинаются жалобы, а не по желанию.
  • За каждым SLO стоит дежурство, деньги и договорённость о праве тормозить релиз. Нет третьего — нет SLO.

Чеклист перед объявлением SLO

  • Назван пользовательский путь, а не сервис
  • Явно определены «хорошее событие» и «значимое событие»; из знаменателя исключены health-чеки, боты и синтетика
  • Указаны точка измерения и её слепые зоны
  • Указаны окно и тип счёта (событийный или временной), и тип больше не меняется
  • Проверено, что цель не выше композиции SLO зависимостей
  • Проверено, что объём трафика позволяет отличить эту цель от соседней
  • Есть владелец с именем; записано, что происходит при исчерпании бюджета
  • Есть дежурство, покрывающее окно SLO, — или окно сужено до рабочих часов; спецификация лежит в репозитории и ревьюится как код

Источники

Что дальше

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

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

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

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

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

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