Алерты: как не утопить дежурного в шуме
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, бюджет ошибок и мониторинг. Мониторинг отвечает на вопрос «что происходит»; алертинг — на гораздо более узкий вопрос «нужно ли из-за этого будить человека».
Определение, из которого всё следует
Алерт — это обязательство прервать человека и потребовать от него действия сейчас.
Не «сигнал», не «уведомление», не «а вдруг пригодится». Обязательство. У него есть стоимость: прерванная работа днём, прерванный сон ночью, износ доверия к каналу. И эта стоимость платится каждый раз, независимо от того, оказался сигнал полезным или нет.
Отсюда три вопроса, на которые правило обязано отвечать «да», чтобы стать пейджером:
- Пользователю уже плохо или станет плохо в ближайший час? Если нет — это не срочно по определению.
- Существует действие, которое дежурный выполнит прямо сейчас? Если нет — вы будите человека, чтобы он посмотрел на график и лёг обратно.
- Это действие не может выполнить автоматика? Если может — пишите автоматику, а алерт вешайте на её отказ.
Rob Ewaschuk, автор канонического текста «My Philosophy on Alerting» (он же основа шестой главы книги SRE), формулирует это ещё жёстче: каждый раз, когда пейджер срабатывает, я должен реагировать с ощущением срочности; я могу реагировать с ощущением срочности лишь несколько раз в день, прежде чем перегорю. Это не про мягкость к людям — это про пропускную способность канала.
или станет плохо
в ближайший час?"} Q1 -->|нет| Q2{"Станет плохо,
если не трогать
неделю?"} Q2 -->|нет| DEL["Удалить правило.
Оно ничего не значит"] Q2 -->|да| TICK["Тикет в бэклог
с дедлайном"] Q1 -->|да| Q3{"Есть действие,
которое дежурный
сделает сейчас?"} Q3 -->|нет| FIX["Это не алерт, а работа:
деградация, ёмкость,
архитектура"] Q3 -->|да| Q4{"Автоматика может
сделать это сама?"} Q4 -->|да| AUTO["Автоматизировать.
Алерт — на отказ
автоматики"] Q4 -->|нет| Q5{"Подождёт
до утра?"} Q5 -->|да| BH["Оповещение
в рабочие часы"] Q5 -->|нет| PAGE["Пейджер
+ ссылка на ранбук"]
Обратите внимание на ветку «есть действие, но его делает автоматика». Это прямой мост к главе про рутину и автоматизацию: алерт, на который дежурный три месяца подряд отвечает одной и той же командой, — это не алерт, это невыполненная задача по автоматизации, оформленная как ночной звонок.
Арифметика шума: почему дежурный перестаёт читать
Дежурный — байесовский агент, даже если никогда не слышал этого слова. Он оценивает 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 мин | абсолютный размер очереди ничего не значит без скорости разбора |
Причин у любого симптома десятки, и они меняются с каждым релизом. Симптомов — единицы, и они привязаны к тому, за что вам платят. Один алерт на симптом заменяет двадцать на причины и, в отличие от них, ловит те поломки, которые вы не предусмотрели.
Из этого правила есть ровно два законных исключения:
- Приближающееся насыщение с длинным горизонтом. «Диск кончится через 4 часа при текущей скорости» — симптом ещё не наступил, но действие требуется до его наступления. Это про ёмкость и запас; такие сигналы почти всегда должны быть тикетом, а не звонком.
- Отказ самой системы наблюдения. Если перестали приходить метрики, все ваши симптомные алерты замолчали — и молчание означает не «всё хорошо», а «мы ослепли». Это тот редкий случай, когда алертить надо на причину, причём из независимого контура (внешняя проба, отдельный 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 %
Верхняя панель показывает, почему быстрое сжигание вообще нужно ловить отдельно: на масштабе месяца первый час аварии — это доли миллиметра линии. Нижняя панель — тот же процесс крупным планом, и на ней видно, где именно ставятся пороги.
Мультиоконная схема
Каноническая конфигурация из 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 на критичном пути (нормальная ситуация для внутреннего сервиса или молодого продукта):
- Удлинить окна. Для 695 событий при 0,5 rps нужно ~23 минуты. Значит, «короткое» окно — 30 минут, длинное — 6 часов. Обнаружение медленнее, но честнее.
- Перейти от долей к событиям. При малом трафике у вас нет статистики — у вас есть события. Алерт «три подряд неуспешные синтетические пробы» понятнее и надёжнее, чем «доля ошибок выше 1,44 %».
- Добавить синтетический трафик. Внешняя проба раз в 30 секунд даёт стабильные 2880 событий в сутки независимо от реальной нагрузки и меряет доступность по времени, а не по запросам. Для низконагруженных сервисов это часто единственный работающий SLI. Обратная сторона: проба ходит по одному сценарию и не видит поломок в остальных.
- Смириться с более слабым 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 % случаев хватает первых двух пунктов.
Что делать с сигналом, который не пейджер
Не «удалить или звонить». Есть четыре судьбы.
Пейджер — срочно и есть что делать; таких сигналов меньшинство. Тикет — есть что делать, но не сейчас: автоматически заведённая задача с дедлайном работает лучше, чем сообщение в чат, которое пролистают. Дашборд — контекст для расследования: метрика нужна, алерт не нужен. Удалить — ни срочности, ни действия; самая частая и самая недоиспользуемая судьба.
Отдельно — верхний левый квадрант: «горит, но сделать нечего». Это не проблема алертинга, это проблема системы: нужна деградация, размыкатель, автомасштабирование. Алерт здесь мучает дежурного бессилием, и это худший вид алерта.
Каскад, шторм и усиливающая петля
Алерт-шторм — не отдельная патология, а частный случай того, что в системном мышлении называется усиливающим контуром. Каскадный отказ и усиливающая петля — буквально одно и то же явление, описанное двумя словарями.
деградировал"] --> B["Клиенты ретраят,
нагрузка растёт"] B --> C["Соседи
насыщаются"] C --> D["Срабатывают
сотни правил"] D --> E["Дежурный не видит
первопричину"] E --> F["Время до
исправления растёт"] F --> A D -.->|"размыкание:
группировка, inhibit"| G["Один сигнал
вместо сотни"] F -.->|"размыкание:
backoff, размыкатель"| H["Нагрузка
не растёт"]
Контур с положительной обратной связью нельзя «перетерпеть» — его надо разомкнуть в конкретном месте. У алертинга есть ровно два места влияния: группировка/подавление (обрывает связь «много поломок → много звонков») и привязка алертов к 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 ''}")
Сложность: analyze — O(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 между тремя часовыми поясами; допущение, что есть кому передать сервис, пока вы чините алертинг; допущение о бесконечной ёмкости хранилища метрик под десятки записывающих правил на каждый сервис.
Минимальная работающая конфигурация для команды из трёх-пяти человек выглядит так и умещается на одну страницу:
- Один-два SLO на пользовательский сценарий, а не на сервис.
- Два правила на SLO: быстрое сжигание (пейджер) и медленное (тикет).
- Один алерт на отказ наблюдаемости (dead man’s switch) — иначе тишина неотличима от здоровья.
- Один алерт на приближающееся насыщение с горизонтом в дни, в тикеты.
- Всё остальное — дашборды и логи, без права звонить.
Итого 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.
- Группировка и подавление размыкают усиливающий контур «много поломок → много звонков → дольше чиним». Число уведомлений за инцидент — метрика качества алертинга.
- Цена измерима: недосып ухудшает решения как алкоголь, дублирование каналов обесценивает каждый, избыточность стоит денег. Где шум неудаляем без изменения правил принятия решений — это организационная задача, и называть её надо так.
Источники
- Rob Ewaschuk. My Philosophy on Alerting — исходный текст, из которого выросла глава 6 книги SRE.
- Google. Site Reliability Engineering, ch. 6: Practical Alerting.
- Google. The Site Reliability Workbook, ch. 5: Alerting on SLOs — мультиоконная схема со всеми таблицами.
- Prometheus. Alerting rules, Alertmanager configuration, Alerting best practices.
- PagerDuty. Incident Response Documentation — открытая практика эскалаций и ролей.
- The Joint Commission. Sentinel Event Alert 50: Medical device alarm safety in hospitals (2013) — количественные данные по усталости от сигналов.
- Van Dongen H. et al. The cumulative cost of additional wakefulness. Sleep, 26(2), 2003.
- Dawson D., Reid K. Fatigue, alcohol and performance impairment. Nature 388, 235 (1997).
- Brendan Gregg. The USE Method — что вообще измерять на ресурсах, прежде чем решать, на что алертить.
Что дальше
Мы разобрались, какие сигналы имеют право звонить и как не превратить пейджер в белый шум. Следующий вопрос — кому он звонит, по какому расписанию, что происходит, если человек не ответил, и во что всё это обходится команде.