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, критический пользовательский путь. Для интернет-магазина их обычно три-четыре: найти товар, добавить в корзину, оплатить, посмотреть статус заказа. Для каждого формулируется, что для пользователя означает «сработало», и это переводится в тип показателя.
«оформить заказ»"] --> B{"Что для пользователя
значит «сработало»?"} B -->|"ответ вообще пришёл"| C["Доступность
доля не-5xx"] B -->|"ответ пришёл быстро"| D["Латентность
доля быстрее порога"] B -->|"ответ правильный"| E["Корректность
доля сверок без расхождений"] B -->|"данные не устарели"| F["Свежесть
доля чтений моложе N минут"] B -->|"задание доехало"| G["Полнота
доля обработанных сообщений"] C --> H{"Есть ли точка,
где это видно
близко к пользователю?"} D --> H E --> H F --> H G --> H H -->|"да"| I["Считаем good / valid
в этой точке"] H -->|"нет"| J["Берём ближайшую доступную
и записываем слепую зону"] I --> K["Цель + окно = SLO"] J --> K
Полный набор типов, который покрывает практически всё:
| Тип SLI | Что считаем | Где особенно уместен |
|---|---|---|
| Доступность | доля запросов без ошибки сервера | синхронные API, веб |
| Латентность | доля запросов быстрее порога | всё интерактивное |
| Корректность | доля результатов, прошедших сверку | биллинг, отчёты, ETL |
| Свежесть | доля чтений, где данные моложе N | кэши, реплики, витрины |
| Полнота | доля входных записей, доехавших до выхода | пайплайны, очереди |
| Пропускная способность | доля времени, когда система тянет заявленный поток | батчи, стриминг |
| Долговечность | доля объектов, которые не потерялись | хранилища, бэкапы |
Для конвейеров данных естественная пара — свежесть и полнота: «97 % партиций готовы к 06:00» и «доля потерянных записей ниже 0,01 %». Доступность там почти ничего не значит.
Сколько SLO заводить? Правило простое: на сервис два-четыре. Один на доступность главного пути, один на латентность, при необходимости один на корректность или свежесть. Двадцать SLO — это ноль SLO: никто не удержит их в голове, и ни одно не будет влиять на решения. Это частный случай локальной оптимизации, разобранной в системном мышлении: оптимизируя всё, вы не оптимизируете ничего.
Где мерить: точка наблюдения решает больше, чем формула
Одна и та же формула, посчитанная в разных точках, даёт разные числа — и разные слепые зоны.
Разница не теоретическая. Если сервис упал целиком, метрика внутри сервиса просто перестаёт поступать — и наивный запрос вида «доля 5xx» покажет отсутствие данных, а не 0 %. Балансировщик в этот момент честно запишет 502 на каждый запрос. Отсюда базовое правило: считайте SLI доступности как можно ближе к пользователю, но там, где объём данных достаточен. Практический компромисс:
- Основной SLI — по логам или метрикам балансировщика/ingress. Видит отказ сервиса, имеет полный объём трафика, дёшев (прокси и балансировка).
- Дополнительный — RUM из клиента. Не как SLO, а как контроль: если RUM систематически хуже серверного SLI на 0,5 %, у вас проблема на пути, который вы не мерите.
- Синтетика — для путей с малым трафиком и для проверки DNS/TLS. Ссылаться на неё в SLO опасно: сотня проб в час — это статистика, где один сбойный прогон даёт 1 % ошибок.
Клиентские замеры ловят то, чего нет в серверных метриках (медленный мобильный интернет, старые устройства, блокировки), но большинство этих проблем вы починить не можете. Мерить полезно, обещать по ним — нет. Об инструментальной стороне — наблюдаемость в распределённых системах и следующая глава трека.
Событийный SLI против временного
Есть два способа считать одно и то же, и они дают разные числа. Событийный (request-based) — доля хороших запросов среди всех. Временной (time-based) — доля «хороших минут» среди всех минут, где минута считается хорошей, если доля ошибок в ней ниже порога.
На картинке — один и тот же четырёхминутный отказ, попавший в пик нагрузки. Событийный счёт даёт 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 %. Рабочий способ — три шага.
- Измерить, как есть. Заведите SLI и смотрите на него месяц-два, ничего не обещая. Почти всегда реальность отличается от ожиданий, причём в обе стороны.
- Сопоставить с болью. Наложите на график обращения в поддержку, отказы от покупки, жалобы в чате. Уровень, на котором начинают жаловаться, — это и есть граница «пользователь заметил».
- Поставить цель чуть выше достигнутого, но ниже идеала. Фактически 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, когда пользователям было плохо; срабатывал ли он, когда всё было хорошо; изменился ли профиль нагрузки; появились ли пути, которых нет в наборе.
Типичные ошибки
- SLI по среднему — среднее прячет хвост, а хвост и есть недовольные пользователи. SLI по пингу
/health— проверяет, что процесс жив, а не что он делает работу; история из начала главы ровно про это. - Цель 100 % или «максимально высокая» — механизм ломается в первый день и дальше игнорируется. Двадцать SLO — ни одно не влияет на решения; два-четыре на сервис.
- Грязный знаменатель — health-чеки, боты, синтетика и внутренние вызовы разбавляют SLI и делают его нечувствительным.
- Перцентиль вместо доли — не складывается, не даёт бюджета, провоцирует усреднение перцентилей. Смешивание событийного и временного счёта в одном разговоре — то же самое, только про бюджет.
- Забыли окно — «99,9 %» без окна не цель, а лозунг. SLO без владельца — никто не отвечает, значит, никто и не смотрит.
- SLO выше, чем позволяют зависимости — обещание, арифметически невыполнимое. SLA = SLO — ноль запаса, первая же просадка сразу стоит денег.
- 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, — или окно сужено до рабочих часов; спецификация лежит в репозитории и ревьюится как код
Источники
- Google, Site Reliability Engineering, гл. 4 «Service Level Objectives»
- Google, The Site Reliability Workbook, гл. 2 «Implementing SLOs»
- Google, The Site Reliability Workbook, гл. 5 «Alerting on SLOs»
- Alex Hidalgo, Implementing Service Level Objectives, O’Reilly, 2020
- OpenSLO — открытая спецификация формата SLO; Sloth — генератор правил Prometheus из неё
- Prometheus: histograms and summaries — про бакеты и почему они должны совпадать с порогами
- Gil Tene, How NOT to Measure Latency — про coordinated omission
- uptime.is — калькулятор бюджета по цели и окну; AWS Compute SLA — как SLA выглядит в реальном договоре
Что дальше
Мы получили число и цель. Само по себе это ещё ничего не меняет: дашборд с зелёной цифрой никого не остановил от пятничного релиза. Работать SLO начинает, когда разница между целью и реальностью превращается в ресурс, который можно потратить, — в бюджет ошибок.
Бюджет ошибок: арифметика и что она меняет в решениях — как считать остаток, что такое скорость прожигания, какие решения меняются при исчерпании бюджета и почему этот механизм ломается без организационной поддержки.