Команда настроила мониторинг, алерты сыплются постоянно — и в какой-то момент никто на них уже не реагирует. А когда случается настоящий инцидент, его замечают спустя час. Это классическая проблема «слепого мониторинга»: много шума, мало сигнала.
SLO — это способ навести порядок: договориться, что именно считается нормальной работой, и алертировать только тогда, когда норма нарушается.
Что такое SLO и зачем он нужен
SLO (Service Level Objective) — это численная цель уровня сервиса. Не «всё работает», а «99.9% запросов успешны за последние 30 дней».
Откуда берётся цифра: бизнес вместе с командой решает, сколько допустимо терять. 99.9% означает, что за 30 дней сервис может «потратить» 43 минуты на сбои — это и есть error budget (бюджет ошибок). Пока бюджет не исчерпан, команда может выпускать релизы, в том числе рискованные. Исчерпался — пауза на стабилизацию.
SLI (Service Level Indicator) — это то, что измеряется. Обычно два вида:
- Availability (доступность): доля запросов без 5xx-ошибок.
- Latency (задержка): p95 или p99 времени ответа.
Почему именно p95, а не среднее? Среднее легко «размазывает» проблемы: если 5% запросов отвечают за 10 секунд, среднее может быть вполне приемлемым. Процентиль показывает, что происходит с реальными пользователями.
Пример SLO для нескольких endpoint'ов:
| Endpoint | Availability | Latency |
|---|---|---|
POST /orders | 99.9% non-5xx | p95 < 500ms |
POST /payments | 99.95% non-5xx | p95 < 1s |
GET /orders/{id} | 99.95% non-5xx | p95 < 200ms |
Выбирать SLO реалистично: 99.99% (52 минуты на весь год) требует совсем другой инфраструктуры — несколько регионов, немедленный failover. Это на порядок дороже 99.9% (8.7 часа в год).
Как считать SLI в Prometheus
Spring Boot с Micrometer автоматически публикует метрику http_server_requests_seconds. Из неё считается SLI:
# Availability SLI: доля успешных запросов за 30 дней
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST",status!~"5.."}[30d]))
/
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST"}[30d]))
# Latency SLI: p95 за 30 дней
histogram_quantile(0.95,
sum by (le) (rate(http_server_requests_seconds_bucket{uri="/orders",method="POST"}[30d]))
)
Результат — число от 0 до 1. Если availability SLI = 0.999, значит 99.9% запросов успешны — SLO выполняется.
Multi-window burn rate: как алертировать правильно
Считать SLI за 30 дней хорошо для отчётов, но для алертов — слишком медленно. Если прямо сейчас сервис падает, узнать об этом через неделю бесполезно.
Другая крайность: алертировать на каждую ошибку. Это приводит к постоянному шуму — команда перестаёт обращать внимание.
Решение из Google SRE Workbook — burn rate (скорость сжигания бюджета).
Как это работает. SLO 99.9% даёт бюджет 0.1% за 30 дней. Если за 1 час ошибок столько, что при таком темпе бюджет кончится за 20 часов — это экстренная ситуация. Число, которое это выражает:
burn rate = (текущая доля ошибок) / (1 - SLO_target)
Для SLO 99.9%:
- burn rate > 14.4 за 1 час — бюджет закончится за ~20 часов. Нужно реагировать немедленно.
- burn rate > 6 за 6 часов — бюджет закончится за ~5 дней. Нужно разобраться до конца рабочего дня.
- burn rate около 1 — нормальный расход, всё в порядке.
Для надёжности берут два окна: если короткое окно показывает пожар, но длинное спокойно — возможно, это кратковременный всплеск, а не тренд. Алерт срабатывает только когда оба окна превышают порог.
- alert: OrdersSloFastBurn
expr: |
(
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST",status=~"5.."}[1h]))
/
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST"}[1h]))
) > (14.4 * (1 - 0.999))
for: 2m
annotations:
runbook: https://runbooks.internal/orders-slo-fast-burn
- alert: OrdersSloSlowBurn
expr: |
(
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST",status=~"5.."}[6h]))
/
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST"}[6h]))
) > (6 * (1 - 0.999))
for: 15m
annotations:
runbook: https://runbooks.internal/orders-slo-slow-burn
Fast burn будит дежурного немедленно — потенциальный серьёзный сбой. Slow burn создаёт задачу в трекере — деградация не критичная, но требует разбора.
Алерт на исчерпание бюджета
Когда от error budget остаётся меньше 10%, это сигнал команде: следующий месяц — не время для рискованных релизов, надо заняться устойчивостью.
Как посчитать остаток бюджета:
# Сколько бюджета осталось: 1 = весь свободен, 0 = весь израсходован
1 - (
(1 - <availability_sli_30d>) / (1 - <slo_target>)
)
Алерт если осталось меньше 10%:
- alert: OrdersErrorBudgetExhausted
expr: <budget_remaining_expression> < 0.1
for: 1h
annotations:
summary: "Только 10% error budget осталось"
description: |
Команда переключается с features на reliability.
Релизы рискованных изменений приостановлены до восстановления бюджета.
Это не «нужно срочно чинить ночью» — это плановый сигнал для планирования следующего спринта.
Алерты за пределами SLO
SLO измеряет то, что видит пользователь. Но есть сигналы, которые предупреждают о проблеме раньше, чем она повлияет на пользователей:
| Что мониторить | Метрика | Что означает |
|---|---|---|
| Память JVM | jvm_memory_used / max > 0.85 | Скоро GC-паузы или OutOfMemory |
| Пул соединений к БД | hikaricp_connections_pending > 0 | Запросы ждут соединения — задержки растут |
| Бизнес-ошибки | order_failed_total rate > 100/мин | Что-то изменилось в данных или логике |
| Circuit Breaker | resilience4j_circuitbreaker_state{state="open"} | Внешний сервис недоступен, идёт на fallback |
| Попадания в кэш | cache_hits / (hits+misses) < 0.7 | Кэш почти не помогает — нагрузка на БД растёт |
| Отставание Kafka | kafka_consumer_lag_max > 10000 | Потребители не успевают за продюсерами |
Например, Circuit Breaker в состоянии open означает, что запросы к внешнему сервису уходят на запасной путь — SLO ещё в норме, но проблема уже есть и может усугубиться.
Частые ошибки
Алерт на каждую ошибку в логах
Один клиент пытается отправить невалидный запрос 1000 раз за минуту — 1000 log.error(...) превращаются в 1000 алертов. Команда начинает их игнорировать. Потом происходит настоящий инцидент, и его замечают слишком поздно.
Правильный подход — алертировать на скорость ошибок конкретного типа, не на каждый отдельный случай:
- alert: HighErrorRate
expr: sum by (exception) (rate(app_errors_total[5m])) > 1
for: 5m
annotations:
runbook: https://runbooks.internal/high-error-rate
Единичный ValidationException — это нормально для плохих входных данных, не алерт. Но если таких исключений 100 в минуту — уже стоит разобраться.
SLO в 100%
100% означает «мы не можем ошибиться никогда». На практике любой релиз становится источником тревоги — любая регрессия сразу нарушение SLO, срочный инцидент. Error budget равен нулю — нечем маневрировать.
99.9% даёт 43 минуты в месяц «законного» времени на сбои. Команда может выпустить рискованное изменение, убедиться в проблеме и откатить — при этом SLO не нарушается.
Алерт без инструкции
PagerDuty в три ночи будит дежурного. Алерт: «p95 latency на /orders > 1s». Без инструкции дежурный открывает Grafana, не знает что смотреть, не знает кому звонить. В итоге — звонок тимлиду в три ночи без какой-либо подготовки.
Хорошая инструкция (runbook) по тому же алерту:
- Проверить
hikaricp_connections_pending— если больше нуля, нужно масштабировать приложение или базу данных. - Проверить
external_calls_duration_seconds{system="payment-provider"}— если больше 2 секунд, инцидент на стороне payment, можно подтвердить получение и ждать. - Если ни первое, ни второе — эскалировать в
#order-service-oncall.
Runbook — обязательная часть каждого алерта. Без него алерт неполный.
Коротко
- SLO — численная цель (например, 99.9% запросов успешны). SLI — то, что реально измеряется в Prometheus.
- Error budget — «разрешённый» объём сбоев. 99.9% даёт 43 минуты в месяц. Пока бюджет не исчерпан, команда может рисковать.
- Burn rate показывает, как быстро расходуется бюджет. Fast burn (1 час, порог 14.4) — немедленная реакция. Slow burn (6 часов, порог 6) — плановый разбор.
- Используй два окна на один алерт: если только короткое окно превышает порог, возможно это всплеск, а не тренд.
- Алерт на исчерпание бюджета (< 10%) — это сигнал для планирования, а не для дежурного.
- Алерты на инфраструктуру (память JVM, пул БД, Circuit Breaker) — отдельно от SLO, предупреждают раньше.
- Не алертируй на каждую ошибку — алертируй на скорость ошибок конкретного типа.
- 100% SLO — это ловушка: любой релиз становится угрозой инцидента.
- Каждый алерт должен иметь runbook: что проверить, что сделать, кому звонить.
Что почитать дальше
- Метрики в Spring Boot — настройка
http_server_requests_secondsдля расчёта SLI. - Трассировка в Spring Boot — детальный разбор проблемных запросов через traces.
- Health checks — почему kubernetes-пробы не заменяют SLO.
- Google SRE Workbook — Alerting on SLOs — первоисточник multi-window burn rate.