SRE и надёжность Алерты: как не утопить дежурного в шуме
0%

Алерты: как не утопить дежурного в шуме

Алерты: как не утопить дежурного в шуме

02:14. Телефон звонит. HighCPUUsage on node-17: 84 % CPU на одной ноде из сорока, пользователи ничего не заметили, через восемь минут само сойдёт. Дежурный нажимает «принял» и ложится обратно. 02:41 — DiskWillFillIn7Days on log-collector-3. Через семь дней. Ночью. 03:06 — KafkaConsumerLagHigh: лаг 4000 сообщений при обычных 800, батч-джоба догонит к утру.

03:52. Checkout5xxRate. Дежурный нажимает «принял», не глядя, — потому что предыдущие три раза это тоже была ерунда. В этот раз это не ерунда: половина платежей падает уже двадцать минут.

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

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

Предполагается, что вы уже прочли про SLI и SLO, бюджет ошибок и мониторинг. Мониторинг отвечает на вопрос «что происходит»; алертинг — на гораздо более узкий вопрос «нужно ли из-за этого будить человека».

Определение, из которого всё следует

Алерт — это обязательство прервать человека и потребовать от него действия сейчас.

Не «сигнал», не «уведомление», не «а вдруг пригодится». Обязательство. У него есть стоимость: прерванная работа днём, прерванный сон ночью, износ доверия к каналу. И эта стоимость платится каждый раз, независимо от того, оказался сигнал полезным или нет.

Отсюда три вопроса, на которые правило обязано отвечать «да», чтобы стать пейджером:

  1. Пользователю уже плохо или станет плохо в ближайший час? Если нет — это не срочно по определению.
  2. Существует действие, которое дежурный выполнит прямо сейчас? Если нет — вы будите человека, чтобы он посмотрел на график и лёг обратно.
  3. Это действие не может выполнить автоматика? Если может — пишите автоматику, а алерт вешайте на её отказ.

Rob Ewaschuk, автор канонического текста «My Philosophy on Alerting» (он же основа шестой главы книги SRE), формулирует это ещё жёстче: каждый раз, когда пейджер срабатывает, я должен реагировать с ощущением срочности; я могу реагировать с ощущением срочности лишь несколько раз в день, прежде чем перегорю. Это не про мягкость к людям — это про пропускную способность канала.

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

Арифметика шума: почему дежурный перестаёт читать

Дежурный — байесовский агент, даже если никогда не слышал этого слова. Он оценивает P(реальная проблема | сработал алерт) по опыту и распределяет внимание пропорционально.

Возьмём конкретное правило: CPU > 80 % в течение 5 минут. За 90 дней оно сработало 312 раз. Из них 7 раз совпало с реальной деградацией для пользователя. Точность:

точность = 7 / 312 = 2,2 %

То есть в 97,8 % случаев звонок был ложным. Человеку не нужно 312 наблюдений — ему хватает тридцати, чтобы выработать корректную стратегию: не реагировать сразу. И эта стратегия рациональна. Проблема в том, что она не избирательна: усталость переносится на весь канал.

Теперь считаем, что это делает с каналом целиком. Пусть за квартал в пейджер приходило два правила:

Правило Страниц Из них по делу Точность
HighCPUUsage 300 6 2 %
Checkout5xxRate 20 18 90 %
Итого канал 320 24 7,5 %

Дежурный видит канал с точностью 7,5 % — то есть примерно тринадцать звонков на один настоящий. Хорошее правило утонуло в плохом. Удаляем одно правило (не чиним, не тюним — просто снимаем с пейджера):

Правило Страниц Из них по делу Точность
Checkout5xxRate 20 18 90 %

Точность канала выросла с 7,5 % до 90 %. Информации не прибавилось ни на бит — просто перестала теряться. Это ключевой вывод главы: алертинг оптимизируют удалением, а не добавлением. Первый рефлекс после инцидента («добавим алерт, чтобы в следующий раз заметить раньше») чаще ухудшает систему, чем улучшает.

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

Аналогия из медицины здесь не литературная, а буквальная. The Joint Commission в Sentinel Event Alert 50 (2013) описывает, что от 85 % до 99 % срабатываний медицинских приборов не требуют вмешательства, — и фиксирует смерти пациентов, вызванные тем, что персонал приглушил или проигнорировал сигнал. Механизм ровно тот же, что у вас в чате дежурных, только цена выше.

Симптом, а не причина

Второе после «удаляйте» правило: алерт вешают на симптом, наблюдаемый пользователем, а не на причину, наблюдаемую инженером.

Причина (плохой алерт) Симптом (хороший алерт) Почему
CPU > 80 % доля 5xx выше SLO высокий CPU может быть нормой; SLO нарушено — всегда плохо
Реплика БД отстала на 2 с p99 чтения выше SLO отставание безвредно, пока читатели его не замечают
Под перезапустился доступность endpoint упала рестарт под нагрузкой — штатное поведение
Свободно 15 % диска запись данных отклоняется 15 % на диске в 8 ТБ — это 1,2 ТБ запаса
Очередь выросла до 10 000 возраст самого старого сообщения > 5 мин абсолютный размер очереди ничего не значит без скорости разбора

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

Из этого правила есть ровно два законных исключения:

  1. Приближающееся насыщение с длинным горизонтом. «Диск кончится через 4 часа при текущей скорости» — симптом ещё не наступил, но действие требуется до его наступления. Это про ёмкость и запас; такие сигналы почти всегда должны быть тикетом, а не звонком.
  2. Отказ самой системы наблюдения. Если перестали приходить метрики, все ваши симптомные алерты замолчали — и молчание означает не «всё хорошо», а «мы ослепли». Это тот редкий случай, когда алертить надо на причину, причём из независимого контура (внешняя проба, отдельный Prometheus, мёртвый человек — dead man’s switch).

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

Сколько минут даёт каждая девятка

Прежде чем строить пороги, зафиксируем арифметику доступности. Окно — 30 дней, это 30 × 24 × 60 = 43 200 минут.

SLO Доля бюджета Бюджет за 30 дней Бюджет за год
99 % 1 % 432 мин = 7 ч 12 мин 3,65 суток
99,5 % 0,5 % 216 мин = 3 ч 36 мин 1,83 суток
99,9 % 0,1 % 43,2 мин 8 ч 46 мин
99,95 % 0,05 % 21,6 мин 4 ч 23 мин
99,99 % 0,01 % 4,32 мин 52,6 мин
99,999 % 0,001 % 25,9 секунды 5,26 мин

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

  • один rolling-restart пода с неудачным preStop (30–60 с деградации);
  • одна смена лидера в кластере БД (типично 10–40 с);
  • один TCP-retransmit-шторм при переключении сетевого пути;
  • прогрев JIT после деплоя на JVM-сервисе.

Иначе говоря, при пяти девятках любое обычное эксплуатационное действие, выполненное чуть менее аккуратно, съедает месячный бюджет целиком. Пять девяток — это не «мы очень стараемся», это архитектура без единой точки, где запрос может подождать человека: несколько независимых зон, автоматическое переключение за секунды, автоматический откат релиза, полное отсутствие ручных операций на горячем пути. Цена такой конструкции растёт нелинейно, а выигрыш для пользователя — почти нулевой, потому что домашний Wi-Fi клиента и его мобильный оператор дают куда больше 26 секунд недоступности в месяц. Об этом же — в главе про надёжность и отказоустойчивость.

Почему пять девяток убивают саму идею алертинга

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

Этап Типичное время
Скрейп метрик 15–30 с
Интервал вычисления правила 15–30 с
for: — подтверждение устойчивости условия 60–120 с
group_wait в Alertmanager 30 с
Доставка (SMS/звонок/push) и звонок телефона 30–60 с
Человек проснулся, нашёл очки, открыл ноутбук 180–300 с
Итого до первого взгляда 5,5–9,5 минуты

Дальше — диагностика и действие, ещё как минимум столько же. Реалистичный MTTR с человеком в контуре — от 10–15 минут.

Сопоставьте с бюджетами: 43,2 минуты (99,9 %) переживают два-три таких инцидента в месяц. 4,32 минуты (99,99 %) не переживают ни одного: пока дежурный надевает очки, месячный бюджет уже сожжён. Вывод не про мотивацию, а про инженерию: начиная с 99,99 % оповещение человека перестаёт быть механизмом защиты SLO и становится механизмом расследования постфактум. Защищать бюджет там должна автоматика — размыкатели, автооткаты, деградация (глава про деградацию вместо отказа и про безопасные релизы).

Оповещение по скорости сжигания бюджета

Теперь главный инструмент. Идея: не алертить на «плохо стало», а алертить на темп расхода бюджета ошибок.

Скорость сжигания (burn rate) — во сколько раз быстрее плана расходуется бюджет. Скорость 1 означает, что при таком темпе бюджет кончится ровно в конце окна. Скорость 14,4 — что он кончится за 30 / 14,4 ≈ 2,1 дня.

Две формулы, из которых выводится вся конфигурация:

доля_ошибок  = (1 - SLO) × скорость          доля_бюджета = скорость × (окно / 720 ч)

SLO 99,9 %:  1x    → 0,1 %                    1   × (72 ч / 720 ч) = 10 %
             3x    → 0,3 %                    3   × (24 ч / 720 ч) = 10 %
             6x    → 0,6 %                    6   × ( 6 ч / 720 ч) =  5 %
             14,4x → 1,44 %                   14,4 × ( 1 ч / 720 ч) =  2 %

Скорость сжигания бюджета ошибок: остаток за 30 дней и первые 6 часов крупным планом

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

Мультиоконная схема

Каноническая конфигурация из SRE Workbook, глава «Alerting on SLOs»:

Скорость Длинное окно Короткое окно Сожжено при срабатывании Куда
14,4x 1 час 5 минут 2 % пейджер
6x 6 часов 30 минут 5 % пейджер
3x 1 сутки 2 часа 10 % тикет
1x 3 суток 6 часов 10 % тикет

Зачем два окна на каждое правило? Длинное задаёт чувствительность и отсекает короткие всплески. Короткое (обычно 1/12 от длинного) нужно, чтобы алерт гас быстро: без него правило с часовым окном продолжает гореть ещё час после того, как всё починили, и дежурный либо ждёт впустую, либо приучается глушить.

Время обнаружения — и почему оно самонастраивается

Красивое свойство схемы: время до срабатывания обратно пропорционально тяжести аварии. Если реальная доля ошибок равна E, а порог правила T = (1 - SLO) × скорость, то длинное окно L наберёт нужную долю через t = L × T / E. Считаем для правила 14,4x с часовым окном, SLO 99,9 % (T = 0,0144):

Реальная доля ошибок Время до срабатывания
100 % (полный отказ) 3600 × 0,0144 / 1,0 = 52 секунды
20 % 3600 × 0,0144 / 0,2 = 4,3 минуты
10 % 8,6 минуты
5 % 17,3 минуты
1,44 % ровно на пороге — не сработает

Один и тот же алерт мгновенно реагирует на катастрофу и терпеливо игнорирует лёгкую деградацию, которая укладывается в бюджет. Ни один статический порог вида «5xx > 1 % пять минут» так не умеет: он одинаково медленно ловит полный отказ и одинаково громко орёт на безобидное дрожание.

Реализация в Prometheus

Считать одно и то же выражение в четырёх правилах на восьми окнах дорого — выносим в записывающие правила.

# prometheus/rules/slo-checkout.yaml
groups:
  # Доля неуспешных запросов считается один раз записывающим правилом
  # на каждое окно (5m, 30m, 1h, 6h) и переиспользуется всеми алертами:
  #   record: job:slo_errors_ratio:rate5m
  #   expr:   sum(rate(http_requests_total{job="checkout",code=~"5.."}[5m]))
  #             / sum(rate(http_requests_total{job="checkout"}[5m]))
  - name: slo_checkout_burn
    rules:
      # 14,4x за час = 2 % месячного бюджета. Будим человека.
      - alert: CheckoutBudgetFastBurn
        expr: |
          (
            job:slo_errors_ratio:rate1h > (14.4 * 0.001)
            and
            job:slo_errors_ratio:rate5m > (14.4 * 0.001)
          )
          # Страховка от малого трафика: без минимума событий
          # доля ошибок — не оценка, а случайное число.
          and sum(increase(http_requests_total{job="checkout"}[1h])) > 5000          
        for: 2m
        labels: {severity: page, slo: checkout-availability}
        annotations:
          summary: "checkout сжигает бюджет ошибок в 14,4 раза быстрее плана"
          description: >-
            Доля 5xx за час — {{ $value | humanizePercentage }}.
            При таком темпе месячный бюджет (43,2 мин) кончится за 2 суток.            
          runbook_url: "https://runbooks.internal/slo/checkout-fast-burn"

      # Правило CheckoutBudgetSlowBurn отличается только числами:
      # окна 6h/30m, порог 6 * 0.001, минимум 30000 событий, for: 15m.

Два момента, которые часто пропускают.

for держите коротким. Короткое окно уже выполняет функцию сглаживания; for: 15m поверх часового окна добавляет пятнадцать минут к времени обнаружения и ничего не отсекает. Правило простое: for нужен там, где нет второго окна.

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

Где эта арифметика ломается: малый трафик

Вот честная граница метода, о которой в книгах пишут мельком.

Порог 14,4x при SLO 99,9 % — это доля ошибок 1,44 %. В пятиминутном окне при нагрузке 1 запрос в секунду это 300 × 0,0144 = 4,32 запроса. То есть пять неудачных запросов за пять минут переводят вашу систему в состояние «будим человека ночью». Пять запросов — это один клиент с протухшим токеном, один сканер, один таймаут в сети. Отношение сигнал/шум при таком количестве событий физически отсутствует.

Сколько событий нужно? Разумная эвристика: чтобы срабатывание не могло быть вызвано горсткой случайных ошибок, требуйте, чтобы порогу соответствовало хотя бы 10 ошибок:

минимум_событий = 10 / ((1 - SLO) × скорость)

SLO 99,9 %,  14,4x:  10 / 0,0144  =  695 событий в окне
SLO 99,9 %,  6x:     10 / 0,006   = 1667 событий в окне
SLO 99,99 %, 14,4x:  10 / 0,00144 = 6945 событий в окне

Для пятиминутного окна это 695 / 300 ≈ 2,3 запроса в секунду. Ниже — пятиминутное окно бессмысленно, и никакой тюнинг порога не поможет: не хватает данных. Что делать, если у вас 0,5 rps на критичном пути (нормальная ситуация для внутреннего сервиса или молодого продукта):

  1. Удлинить окна. Для 695 событий при 0,5 rps нужно ~23 минуты. Значит, «короткое» окно — 30 минут, длинное — 6 часов. Обнаружение медленнее, но честнее.
  2. Перейти от долей к событиям. При малом трафике у вас нет статистики — у вас есть события. Алерт «три подряд неуспешные синтетические пробы» понятнее и надёжнее, чем «доля ошибок выше 1,44 %».
  3. Добавить синтетический трафик. Внешняя проба раз в 30 секунд даёт стабильные 2880 событий в сутки независимо от реальной нагрузки и меряет доступность по времени, а не по запросам. Для низконагруженных сервисов это часто единственный работающий SLI. Обратная сторона: проба ходит по одному сценарию и не видит поломок в остальных.
  4. Смириться с более слабым SLO. 99,5 % (216 минут в месяц) на малом трафике измеряется устойчиво, 99,99 % — нет. Обещать точность, которой у вас нет измерительного инструмента, — самообман, который вскроется на первом же споре о том, был инцидент или не был.

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

Путь сигнала: от правила до человека

Между expr в Prometheus и звонком телефона лежит слой, который отвечает за то, чтобы двадцать связанных алертов не превратились в двадцать звонков.

Ключевые механизмы, каждый из которых убирает целый класс шума:

  • Группировка (group_by) — все алерты одного SLO приходят одним сообщением. Без неё каскадный отказ на сорока нодах даёт сорок звонков.
  • Подавление (inhibit_rules) — если упал весь кластер, алерты отдельных сервисов внутри него бессмысленны. Самая недооценённая настройка: она превращает алерт-шторм в один сигнал.
  • Дедупликация — два экземпляра Prometheus, шлющие один и тот же алерт, дают одно уведомление.
  • Тишина (silence) — на время планового обслуживания, обязательно с истечением: бессрочная тишина — способ навсегда потерять алерт.
  • Маршрутизация по времениseverity=ticket не имеет права звонить ночью.
# alertmanager.yaml
route:
  group_by: ["slo", "cluster"]
  group_wait: 30s        # подождать соседей перед первой отправкой
  group_interval: 5m     # как часто досылать изменения в группу
  repeat_interval: 4h    # напоминание, если алерт всё ещё горит
  receiver: chat-default
  routes:
    - matchers: ['severity="page"']
      receiver: pagerduty
      group_wait: 10s    # для страниц ждём меньше
    - matchers: ['severity="ticket"']
      receiver: issue-tracker
      # business-hours описывается в time_intervals: пн–пт, 10:00–19:00,
      # location: Europe/Moscow. Ночью тикеты не беспокоят никого.
      active_time_intervals: [business-hours]

inhibit_rules:
  # Отказ кластера гасит алерты отдельных сервисов в этом кластере:
  # именно это превращает алерт-шторм в один сигнал.
  - source_matchers: ['severity="page"', 'scope="cluster"']
    target_matchers: ['severity="page"', 'scope="service"']
    equal: ["cluster"]

Документация: правила алертов Prometheus, конфигурация Alertmanager, практики алертинга.

Жизненный цикл алерта

Понимание состояний экономит часы отладки «почему не пришло» и «почему пришло дважды».

В состоянии Pending живёт for: слишком большой — задержка обнаружения, слишком малый — флаппинг. Переход Notified → Resolved без Acked — важнейший диагностический сигнал. Если правило регулярно чинится само до того, как человек успел посмотреть, оно не заслуживает быть пейджером: либо увеличьте for, либо переведите в тикеты, либо удалите.

Флаппинг — метрика дрожит вокруг порога, алерт мигает. Лечится тремя способами, в порядке предпочтения: (1) взять окно подлиннее — rate(...[5m]) вместо [1m]; (2) добавить for; (3) сделать гистерезис — зажигать при 5 %, гасить при 2 %. В Prometheus гистерезиса нет из коробки, его эмулируют через два правила и inhibit, но в 90 % случаев хватает первых двух пунктов.

Что делать с сигналом, который не пейджер

Не «удалить или звонить». Есть четыре судьбы.

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

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

Каскад, шторм и усиливающая петля

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

Контур с положительной обратной связью нельзя «перетерпеть» — его надо разомкнуть в конкретном месте. У алертинга есть ровно два места влияния: группировка/подавление (обрывает связь «много поломок → много звонков») и привязка алертов к SLO верхнего уровня, а не к каждому компоненту. Всё остальное размыкание — в коде: экспоненциальные задержки ретраев, ограничение параллелизма, размыкатели. Про это подробно в главе про деградацию и про режимы отказа; про задержки в контуре управления — в главе про задержки.

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

Ранбук: алерт без него недоделан

Каждое правило, имеющее право звонить, обязано нести runbook_url. Ранбук — не документация архитектуры, а короткий текст в жёсткой структуре: (1) что этот алерт означает — одним предложением на языке пользовательской боли; (2) как проверить, что это не ложное срабатывание — конкретный запрос, конкретный дашборд; (3) что делать в первую очередь — команды, которые можно скопировать, причём смягчение раньше диагностики: откатить релиз, перевести трафик, включить деградированный режим; (4) когда эскалировать и кому — по имени роли, не человека; (5) чего делать нельзя — «не рестартить праймари, пока не проверен лаг реплики».

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

Измеряйте сами алерты

Алертинг — такая же система, как сервис, и у неё должны быть свои показатели. Минимальный набор: страниц на смену (ориентир Google — не больше двух за 12 часов; для маленькой команды честнее считать на человека в неделю: больше 5 — тревожно, больше 10 — команда деградирует); доля действенных — сколько страниц привело к реальному действию, а не к «посмотрел и лёг» (ниже 50 % канал теряет смысл); доля ночных; доля самопочинившихся до ack — прямые кандидаты на удаление; топ правил по бесполезному шуму — обычно 80 % шума дают 2–3 правила.

Скрипт, который считает это по истории и выдаёт список на удаление:

"""Разбор истории страниц: какие правила стоят дежурному дороже, чем дают."""
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime, time

NIGHT = (time(23, 0), time(7, 0))
NIGHT_WEIGHT = 3.0  # бесполезная ночная страница втрое дороже дневной


@dataclass(frozen=True)
class Page:
    rule: str            # имя правила
    fired_at: datetime   # локальное время дежурного, не UTC
    acted: bool          # выполнено действие, а не просто нажат ack


def is_night(ts: datetime) -> bool:
    t = ts.time()
    return t >= NIGHT[0] or t < NIGHT[1]


def analyze(pages: list[Page]) -> dict[str, dict[str, float]]:
    """Один проход по истории: O(n) по времени, O(k) по памяти (k — правил)."""
    stats = defaultdict(lambda: {"pages": 0, "acted": 0, "night": 0})
    for p in pages:
        s = stats[p.rule]
        s["pages"] += 1
        s["acted"] += int(p.acted)
        s["night"] += int(is_night(p.fired_at))
    for s in stats.values():
        # Действенность: ниже 0,5 — правило пора снимать с пейджера.
        s["actionability"] = s["acted"] / s["pages"]
        useless = s["pages"] - s["acted"]
        night = min(s["night"], useless)
        s["noise"] = (useless - night) + NIGHT_WEIGHT * night
    return dict(stats)


def report(pages: list[Page], top: int = 10) -> None:
    """Сортировка k правил: O(k log k). Печатает кандидатов на удаление."""
    stats = analyze(pages)
    total_noise = sum(s["noise"] for s in stats.values()) or 1.0
    for name, s in sorted(stats.items(), key=lambda kv: -kv[1]["noise"])[:top]:
        share = s["noise"] / total_noise
        print(f"{name:<30}{s['pages']:>6}{s['actionability']:>7.0%}"
              f"{s['night']:>6}{share:>7.0%}"
              f"{'  СНЯТЬ С ПЕЙДЖЕРА' if share > 0.3 else ''}")

Сложность: analyzeO(n) по времени и O(k) по памяти (n — страниц, k — правил); report добавляет O(k log k) на сортировку. На десятках тысяч страниц за квартал это доли секунды: узкое место — выгрузка из пейджер-системы, а не счёт.

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

Цена

Здесь заканчивается инженерия и начинается разговор, который многие обходят.

Люди. Ночная страница стоит не «двадцать минут дежурного». Van Dongen и соавторы в работе 2003 года показали, что две недели по 6 часов сна дают когнитивный дефицит, сопоставимый с двумя сутками полного лишения сна, — причём испытуемые субъективно оценивали своё состояние почти как нормальное. Dawson и Reid в Nature (1997) измерили, что 17 часов без сна ухудшают выполнение задач примерно как 0,05 % алкоголя в крови, а 24 часа — как 0,1 %. Практический перевод: инженер после двух разбуженных ночей принимает решения в проде на уровне человека, которому вы бы не дали сесть за руль. Это не аргумент про заботу — это аргумент про качество инженерных решений в момент, когда они важнее всего.

Отсюда правило, которое нужно защищать перед менеджментом цифрами, а не эмоциями: после ночной эскалации следующий день у человека нерабочий. Не «пусть отоспится, если получится» — нерабочий по умолчанию, заложенный в план. Со стороны руководителя эта арифметика разобрана в главе про дежурства и инциденты; здесь — сторона инженера. Как устроено само расписание, эскалация и компенсация — в следующей главе про дежурство.

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

Организация. А теперь место, где инженерное решение упирается в организационное, и никакой YAML этого не чинит.

Шум неудаляем, если удаление требует согласия владельца сервиса, а владелец боится «пропустить». Тогда каждое правило бессмертно, список растёт монотонно, и через два года у вас 400 правил, из которых работают шесть. Технически задача решается за час; организационно — не решается никогда, пока не изменено правило принятия решений.

Работающие формулировки, которые надо провести через команду и руководство:

  • Право дежурного на удаление. Человек, которого разбудили, снимает правило с пейджера в одностороннем порядке — с записью в постмортеме. Возражать можно, но постфактум и с обоснованием.
  • Срок жизни правила. Не приведшее ни к одному действию за 90 дней автоматически разжалуется в тикеты. Хочешь вернуть — обоснуй.
  • Владелец алерта = владелец пейджера. Кто пишет правило, тот на него и дежурит. Возможность создать алерт, который будит кого-то другого, — главный источник шума в организациях с выделенной эксплуатацией.
  • «Алерт-шторм» — полноценный инцидент с постмортемом, а не «ну да, пошумело».

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

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

Книги SRE и SRE Workbook — лучший доступный материал по теме, и они бесплатны. Но они написаны организацией с тысячами сервисов, выделенными SRE-командами и трафиком, при котором статистика работает по определению. Прямой перенос ломается.

Переносится почти без изменений: алертинг на симптомы, а не на причины; обязательный runbook_url в каждом правиле; принцип «алерт = требование действия»; приоритизация через бюджет ошибок; регулярная ревизия правил с правом удаления. Всё это не зависит от масштаба и стоит ноль.

Переносится с оговорками:

  • Мультиоконный burn rate требует трафика. Ниже ~2–3 rps на SLI берите окна длиннее или переходите на синтетические пробы и счёт событий.
  • Норматив «не более двух страниц за смену» предполагает существование смен. В команде из трёх человек 24/7-ротация означает, что каждый дежурит треть жизни; норматив пересчитывайте на неделю, а не на смену.
  • Разделение «SRE пишет алерты, разработчик пишет код» — в маленькой команде это одни и те же люди, и это скорее плюс: автор кода видит цену собственного шума лично.

Не переносится: отдельная роль сортировщика алертов; follow-the-sun между тремя часовыми поясами; допущение, что есть кому передать сервис, пока вы чините алертинг; допущение о бесконечной ёмкости хранилища метрик под десятки записывающих правил на каждый сервис.

Минимальная работающая конфигурация для команды из трёх-пяти человек выглядит так и умещается на одну страницу:

  1. Один-два SLO на пользовательский сценарий, а не на сервис.
  2. Два правила на SLO: быстрое сжигание (пейджер) и медленное (тикет).
  3. Один алерт на отказ наблюдаемости (dead man’s switch) — иначе тишина неотличима от здоровья.
  4. Один алерт на приближающееся насыщение с горизонтом в дни, в тикеты.
  5. Всё остальное — дашборды и логи, без права звонить.

Итого 5–8 правил, способных разбудить. Это не бедность конфигурации, это её зрелость: каждое из них попало туда, пережив вопрос «а что дежурный сделает в три часа ночи». Развёрнуто — в финальной главе про SRE в небольшой команде.

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

  • Алерт на каждый график. Дашборд и алерт — разные инструменты; метрика бывает ценной для расследования и бесполезной для оповещения.
  • Пороги «из головы». 80 % CPU, 1000 сообщений в очереди, 500 мс latency — числа из чужой презентации. Порог обязан выводиться из SLO или из измеренного поведения вашей системы.
  • Слишком большой for. for: 15m поверх часового окна — пятнадцать подаренных аварии минут.
  • Отсутствие минимума событий в знаменателе. Ночной звонок из-за трёх ошибок при трёх запросах.
  • Алерт без ранбука. Дежурный тратит первые двадцать минут на выяснение, что вообще означает KafkaISRShrink.
  • Бессрочные silence. Заглушили на время релиза, забыли снять, через месяц пропустили настоящую аварию.
  • Алерты в чат, где уведомления отключены у всех. Формально сигнал есть, фактически нет — и это хуже отсутствия, потому что создаёт ложную уверенность.
  • Новый алерт как единственное действие по итогам инцидента. Почти всегда признак того, что первопричину не нашли (про постмортемы).
  • Мониторинг мониторинга без независимости. Алерт на отказ Prometheus, живущий внутри того же Prometheus, — не алерт.

Мини-итог

  • Алерт — обязательство прервать человека; его цена платится при каждом срабатывании, независимо от полезности. Три вопроса на входе: пользователю плохо? есть действие? автоматика не справится? Три «да» — пейджер, иначе тикет, дашборд или удаление.
  • Одно шумное правило обесценивает весь канал: 300 страниц точности 2 % рядом с 20 страницами точности 90 % дают канал точности 7,5 %. Алертинг оптимизируют удалением, а не добавлением.
  • Алертить надо на симптомы. Исключения: приближающееся насыщение и отказ самой наблюдаемости.
  • 99,9 % = 43,2 минуты в месяц; 99,999 % = 26 секунд. Конвейер оповещения с человеком в контуре имеет задержку 5–10 минут, поэтому начиная с 99,99 % защищать SLO должна автоматика, а не дежурный.
  • Пороги выводятся из скорости сжигания бюджета: 14,4x за час (2 % бюджета) и 6x за 6 часов (5 %) — на пейджер; 3x за сутки и 1x за трое — в тикеты. Время обнаружения само подстраивается под тяжесть: 52 секунды при полном отказе, 8,6 минуты при 10 % ошибок.
  • Схема требует трафика: при SLO 99,9 % и пороге 14,4x нужно ~695 событий в окне. Ниже 2–3 rps — длинные окна, счёт событий или синтетические пробы, и более скромное SLO.
  • Группировка и подавление размыкают усиливающий контур «много поломок → много звонков → дольше чиним». Число уведомлений за инцидент — метрика качества алертинга.
  • Цена измерима: недосып ухудшает решения как алкоголь, дублирование каналов обесценивает каждый, избыточность стоит денег. Где шум неудаляем без изменения правил принятия решений — это организационная задача, и называть её надо так.

Источники

Что дальше

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

Дежурство: расписание, эскалация и цена для людей

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

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

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

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