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

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

SLO — это способ навести порядок: договориться, что именно считается нормальной работой, и алертировать только тогда, когда норма нарушается.

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

SLO 99.9% на POST /orders · бюджет ошибок 43 минуты на 30 днейburn rate = доля 5xx ÷ 0.1% · порог 14.4 в окне 1 час, порог 6 в окне 6 часов шкала времени: 0 → 6 часов, деление — 1 часдоля 5xx в окне → burn rate Всплеск: 30% 5xx две минутыоба порога мимо — никто не звонитсъедено 36 с бюджета из 43 минутокно 1 ч: 1.0% → 10 (порог 14.4)окно 6 ч: 0.17% → 1.7 (порог 6) Деградация: 0.9% 5xx шесть часовslow burn — задача в трекерсъедено 3.2 минуты из 43окно 1 ч: 0.9% → 9 (порог 14.4)окно 6 ч: 0.9% → 9 (порог 6) Пожар: 6% 5xx сорок минутfast burn — будят дежурногосъедено 2.4 минуты из 43окно 1 ч: 4.0% → 40 (порог 14.4)окно 6 ч: 0.67% → 6.7 (порог 6) Решает не доля ошибок, а скорость расхода бюджета30% две минуты стоят 36 секунд, 0.9% шесть часов — 3.2 минуты из 43

Всплеск 30% за две минуты — самая заметная доля ошибок и самая дешёвая: 36 секунд бюджета, оба окна ниже порога. Тихие 0.9% шесть часов подряд съедают 3.2 минуты из 43 и поднимают slow burn. Кого будить, решает не доля ошибок, а то, за какое окно и с какой скоростью уходит бюджет.

Что такое 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 для нескольких эндпоинтов:

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 минуты на весь год) требует совсем другой инфраструктуры — несколько регионов, мгновенное переключение на резерв. Это на порядок дороже 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 выполняется.

Что считать успешным запросом

В запросе выше стоит status!~"5..", и за этой короткой строчкой прячется решение, вокруг которого при внедрении SLO спорят дольше всего. От определения «успеха» зависит вообще всё: и цифра SLI, и то, будят ли дежурного.

Ответы 4xx. По умолчанию их считают успехом сервиса: 400 на неверный запрос и 404 на несуществующий заказ — это правильная работа, а не отказ. Два исключения. Первое: 429 и 503 при исчерпании лимитов — формально клиент виноват, фактически сервис отказал в обслуживании, и пользователь видит отказ; такие коды берут в ошибки. Второе: всплеск 400 после выката почти всегда означает, что сломались вы (изменили проверку, поехал разбор запроса) — SLI этого не покажет, поэтому на резкий рост доли 4xx ставят отдельную тревогу, отдельно от SLO.

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

Служебные пути. Проверки здоровья, /actuator/*, метрики, синтетические пробы исключают из SLI: они многочисленны, всегда успешны и разбавляют картину — при десяти проверках в секунду и одном настоящем запросе SLI покажет отличную доступность на полностью сломанном сервисе. Исключают по метке пути, а синтетические пробы — по заголовку, которым их помечают.

Что ещё берут в ошибки, хотя код 200. Ответ, отданный из запасного пути с пустым результатом («поиск недоступен, показали ничего»), — формально 200, фактически пользователь не получил того, за чем пришёл. Если такие ответы важны, их помечают в метрике отдельно и включают в числитель ошибок. Это приближает SLI к тому, что чувствует человек, и одновременно делает его сложнее — поэтому начинают с простого определения и уточняют после первого спора «почему SLI зелёный, а жалобы есть».

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

Одна оговорка про [30d]. Посчитать так разово для отчёта можно, но на живой базе этот запрос перебирает месяц точек и считается десятки секунд — дашборд с ним будет отваливаться по таймауту. Штатный способ — записывающие правила: Prometheus раз в минуту сам считает пятиминутные скорости и складывает их в отдельные ряды, а месячный SLI считается уже по ним.

groups:
  - name: orders-sli
    rules:
      - record: orders_post:requests:rate5m
        expr: sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST"}[5m]))
      - record: orders_post:requests_ok:rate5m
        expr: sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST",status!~"5.."}[5m]))

Тогда SLI за месяц — это avg_over_time(orders_post:requests_ok:rate5m[30d]) / avg_over_time(orders_post:requests:rate5m[30d]), и считается он мгновенно.

Multi-window burn rate: как алертировать правильно

Считать SLI за 30 дней хорошо для отчётов, но для алертов — слишком медленно. Если прямо сейчас сервис падает, узнать об этом через неделю бесполезно.

Другая крайность: алертировать на каждую ошибку. Это приводит к постоянному шуму — команда перестаёт обращать внимание.

Решение из Google SRE Workbook — burn rate (скорость сжигания бюджета).

Как это работает. SLO 99.9% даёт бюджет 0.1% за 30 дней. Если за 1 час ошибок столько, что при таком темпе месячный бюджет кончится за пару суток — это экстренная ситуация. Число, которое это выражает:

burn rate = (текущая доля ошибок) / (1 - SLO_target)

Для SLO 99.9%:

  • burn rate > 14.4 за 1 час — бюджет тридцати дней уходит за 30 / 14.4 ≈ 2 суток, а за сам этот час сгорает 2% бюджета. Нужно реагировать немедленно.
  • burn rate > 6 за 6 часов — бюджет закончится за ~5 дней. Нужно разобраться до конца рабочего дня.
  • burn rate около 1 — нормальный расход, всё в порядке.

Fast burn и slow burn — это два отдельных алерта, у каждого своё окно и свой порог: 14.4 за 1 час и 6 за 6 часов. Событию не нужно пройти оба порога сразу: сорокаминутный сбой на 6% ошибок даёт burn rate 40 за час и всего 6.7 за шесть часов — будит дежурного первый алерт.

окно 1 час сбой 40 мин из 60 мин доля 4 % burn 40 окно 6 часов тот же сбой из 360 мин доля 0,67 % burn 6,7 фон 0,9 % льётся 6 часов из 360 мин доля 0,9 % burn 9

Одно и то же событие в двух окнах: сорокаминутный сбой в часовом окне даёт скорость 40 и пробивает порог 14,4, в шестичасовом размывается до 6,7, а ровный фон 0,9 процента размывать нечем, и его ловит только длинное окно с порогом 6.

В полном рецепте Google к каждому алерту добавляют ещё короткое окно-подтверждение (5 минут для fast burn, 30 минут для slow burn), чтобы алерт погас сразу после того, как всплеск закончился, а не висел ещё час. В примере ниже эту роль играет for.

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

(
  sum(rate(...{status=~"5.."}[1h])) / sum(rate(...[1h])) > (14.4 * 0.001)
)
and
(
  sum(rate(...{status=~"5.."}[5m])) / sum(rate(...[5m])) > (14.4 * 0.001)
)

И порогов в полном рецепте не два, а четыре: 14,4 за час (будит немедленно), 6 за 6 часов (разобраться в рабочее время), 3 за сутки и 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_url: 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_url: https://runbooks.internal/orders-slo-slow-burn

Fast burn будит дежурного немедленно — потенциальный серьёзный сбой. Slow burn создаёт задачу в трекере — деградация не критичная, но требует разбора.

Алерт на задержку, а не только на ошибки

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

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

Как правильно. Цель формулируют как долю: «99 % запросов быстрее 500 мс за 30 дней». Тогда хорошие запросы — это те, что попали в бакет гистограммы до границы 500 мс, и их число даёт сам Prometheus:

# доля запросов быстрее 500 мс
sum(rate(http_server_requests_seconds_bucket{uri="/orders",method="POST",le="0.5"}[5m]))
  /
sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST"}[5m]))

Здесь важна одна деталь, которая связывает эту статью с настройкой: граница le="0.5" должна существовать. Бакеты берутся не из воздуха — их задаёт настройка целевых границ (management.metrics.distribution.slo), и если в ней нет ровно 500 мс, запрос вернёт пустоту или посчитает по соседней границе, то есть неправильно. Поэтому порядок такой: сначала договорились о цели, потом прописали её границу в настройках сервиса, потом написали правило. Об этом — статья про конфигурацию.

Дальше всё как с ошибками: доля плохих — это 1 - <доля быстрых>, и она подставляется в тот же расчёт скорости сжигания с теми же порогами:

- alert: OrdersLatencySloFastBurn
  expr: |
    (
      1 -
      sum(rate(http_server_requests_seconds_bucket{uri="/orders",method="POST",le="0.5"}[1h]))
      /
      sum(rate(http_server_requests_seconds_count{uri="/orders",method="POST"}[1h]))
    ) > (14.4 * (1 - 0.99))
  for: 2m
  annotations:
    runbook_url: https://runbooks.internal/orders-latency-slo

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

1 апреля бюджет 43 мин 5 апреля сбой 12 мин 5 апреля остаток 31 мин 20 апреля сбой 25 мин 20 апреля остаток 6 мин 5 мая старый сбой вне окна 5 мая остаток 18 мин

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

Алерт на исчерпание бюджета

Когда от error budget остаётся меньше 10%, это сигнал команде: следующий месяц — не время для рискованных релизов, надо заняться устойчивостью.

Как посчитать остаток бюджета:

# Сколько бюджета осталось: 1 = весь свободен, 0 = весь израсходован
1 - (
  (1 - avg_over_time(orders_post:requests_ok:rate5m[30d])
       / avg_over_time(orders_post:requests:rate5m[30d]))
  / (1 - 0.999)
)

Алерт если осталось меньше 10%:

- alert: OrdersErrorBudgetExhausted
  expr: |
    1 - (
      (1 - avg_over_time(orders_post:requests_ok:rate5m[30d])
           / avg_over_time(orders_post:requests:rate5m[30d]))
      / (1 - 0.999)
    ) < 0.1
  for: 1h
  annotations:
    summary: "Только 10% error budget осталось"
    description: |
      Команда переключается с новых возможностей на надёжность.
      Релизы рискованных изменений приостановлены до восстановления бюджета.

Это не «нужно срочно чинить ночью» — это плановый сигнал для планирования следующего спринта.

Низкий трафик ломает этот рецепт

Всё, что посчитано выше, держится на предположении, что запросов много. Как только их мало, скорость сжигания начинает врать, и это самая частая причина, по которой команды выключают такие тревоги через месяц.

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

Что с этим делают, по возрастанию усилий:

Порог по абсолютному числу. К условию добавляют «и запросов было не меньше N»: тогда одна ошибка на десяти запросах тревогу не создаёт.

(
  sum(rate(...{status=~"5.."}[1h])) / sum(rate(...[1h])) > (14.4 * 0.001)
)
and
sum(increase(...[1h])) > 100

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

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

Объединение. Один эндпоинт с редким трафиком не имеет своего SLO: его берут в общую цель сервиса, где запросов достаточно.

И тревога на пропажу самих метрик

У всех правил выше есть общее слепое пятно: они считают долю. Если метрики перестали приходить вовсе — сервис умер целиком, упал сборщик, сломалась метка после переименования — доля не вычисляется, условие не выполняется, и ни одна тревога не срабатывает. Мониторинг молчит именно тогда, когда всё плохо.

Закрывается это отдельным правилом, которое смотрит не на значение, а на наличие ряда:

- alert: OrdersMetricsMissing
  expr: absent(http_server_requests_seconds_count{job="order-service"}) == 1
  for: 10m
  annotations:
    summary: "Метрики order-service не приходят 10 минут"
    runbook_url: https://runbooks.internal/metrics-missing

Пара замечаний по применению. Срок в for берут больше обычного (пять-десять минут), иначе правило будет срабатывать при каждом выкате, когда старые копии уже погашены, а новые ещё не опрошены. Проверяют наличие самого частого ряда сервиса, а не редкого бизнес-счётчика, который законно молчит ночами. И к этому же семейству относится тревога на состояние опроса (up == 0) и на возраст последней точки: вместе они отвечают на вопрос «мониторинг вообще работает?», который иначе никто не задаёт, пока не понадобится.

Алерты за пределами 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
Попадания в кэшsum(rate(cache_gets_total{result="hit"}[5m])) / sum(rate(cache_gets_total[5m])) < 0.7Кэш почти не помогает — нагрузка на БД растёт
Отставание Kafkakafka_consumer_fetch_manager_records_lag_max > 10000Потребители не успевают за продюсерами

Например, Circuit Breaker в состоянии open означает, что запросы к внешнему сервису уходят на запасной путь — SLO ещё в норме, но проблема уже есть и может усугубиться.

Кто назначает цифру

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

Цифру предлагает команда, соглашается владелец продукта. Механика такая: команда смотрит, какая доступность уже есть сейчас (SLI за последние три месяца), и предлагает цель чуть ниже достигнутого — а не круглое число из презентации. Если сервис фактически держит 99,95 %, цель 99,9 % честна: она достижима и оставляет запас. Цель 99,99 % при фактических 99,95 % означает, что цель нарушена с первого дня и её перестанут замечать через неделю.

Разговор ведут не в процентах, а в минутах и в последствиях. «99,9 % — это 43 минуты недоступности в месяц; при 99,99 % — 4 минуты». И встречный вопрос к продукту: сколько стоит час простоя и что вы готовы за это заплатить. Потому что каждая дополнительная девятка — это не старание, а деньги и отказ от скорости: резерв в другом регионе, дублирование, длинные проверки перед выкатом, дежурство круглосуточно. Обычно на этом месте выясняется, что 99,99 % никому не нужны.

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

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

Что записывают. Одна страница на сервис: операции, для каждой — определение успеха, цель, окно (обычно 30 дней), кто владелец, что делает команда при исчерпании бюджета. Последний пункт — самый важный и самый пропускаемый: договорённость «при бюджете ниже 10 % мы останавливаем рискованные изменения и занимаемся устойчивостью» должна быть согласована до того, как бюджет кончится. Иначе разговор об остановке выката произойдёт в момент, когда все раздражены, и закончится тем, что цель просто поднимут.

Частые ошибки

Алерт на каждую ошибку в логах

Один клиент пытается отправить невалидный запрос 1000 раз за минуту — 1000 log.error(...) превращаются в 1000 алертов. Команда начинает их игнорировать. Потом происходит настоящий инцидент, и его замечают слишком поздно.

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

- alert: HighErrorRate
  expr: sum by (exception) (rate(app_errors_total[5m])) > 1
  for: 5m
  annotations:
    runbook_url: https://runbooks.internal/high-error-rate

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

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: что проверить, что сделать, кому звонить.
  • Определение успеха записывают рядом с целью: 4xx обычно успех, кроме 429 и отказов по лимитам, служебные пути и пробы исключают, ответы из запасного пути стоит считать отказом.
  • Задержку тоже считают бюджетом, но не перцентилем, а долей запросов быстрее границы; граница должна существовать в настройке бакетов, иначе запрос молча считает не то.
  • При малом трафике одна ошибка даёт скорость сжигания в сотни: нужен порог по абсолютному числу запросов, длинные окна или цель на срок вместо доли.
  • Правила на долю молчат, когда метрики пропали совсем: отдельная тревога на absent() и на состояние опроса с запасом в несколько минут на время выката.
  • Цифру предлагает команда по факту достигнутого, а соглашается владелец продукта в минутах и деньгах; доступность не бывает выше доступности зависимостей, а цели ставят на две-четыре пользовательские операции.

Что почитать дальше