← назад к разделу

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

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'ов:

EndpointAvailabilityLatency
POST /orders99.9% non-5xxp95 < 500ms
POST /payments99.95% non-5xxp95 < 1s
GET /orders/{id}99.95% non-5xxp95 < 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 Workbookburn 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 измеряет то, что видит пользователь. Но есть сигналы, которые предупреждают о проблеме раньше, чем она повлияет на пользователей:

Что мониторитьМетрикаЧто означает
Память JVMjvm_memory_used / max > 0.85Скоро GC-паузы или OutOfMemory
Пул соединений к БДhikaricp_connections_pending > 0Запросы ждут соединения — задержки растут
Бизнес-ошибкиorder_failed_total rate > 100/минЧто-то изменилось в данных или логике
Circuit Breakerresilience4j_circuitbreaker_state{state="open"}Внешний сервис недоступен, идёт на fallback
Попадания в кэшcache_hits / (hits+misses) < 0.7Кэш почти не помогает — нагрузка на БД растёт
Отставание Kafkakafka_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) по тому же алерту:

  1. Проверить hikaricp_connections_pending — если больше нуля, нужно масштабировать приложение или базу данных.
  2. Проверить external_calls_duration_seconds{system="payment-provider"} — если больше 2 секунд, инцидент на стороне payment, можно подтвердить получение и ждать.
  3. Если ни первое, ни второе — эскалировать в #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.