SRE и надёжность Мониторинг: метрики, логи, трейсы и что из этого когда
0%

Мониторинг: метрики, логи, трейсы и что из этого когда

Мониторинг: метрики, логи, трейсы и что из этого когда

В главе про SLI и SLO мы выбирали показатель, который что-то значит для пользователя. В главе про бюджет ошибок считали, сколько этого показателя можно потратить. Обе главы молча опирались на допущение, которое пора вскрыть: что показатель вообще откуда-то берётся, и берётся правильно.

Это допущение неверно по умолчанию. SLO «99,9 % запросов быстрее 300 мс» невозможно проверить, если гистограмма латентности собрана с границами бакетов 0,25 и 0,5 секунды — между ними нет числа 0,3, и никакой запрос к хранилищу его не достанет. SLO «99,95 % успешных ответов» будет весело выполняться, пока падает DNS: сервер не видит запросов, которых до него не дошли, а значит и ошибок у него ноль. Мониторинг — это не «графики, чтобы смотреть», а измерительная система, у которой есть разрешение, систематическая ошибка, слепые зоны и цена. Эта глава — про устройство четырёх типов сигналов, про то, какой класс вопросов каждый закрывает и сколько стоит, и про то, где инженерное решение упирается в организационное. Как из сигналов делать поводы кого-то будить — следующая глава.

Мониторинг отвечает на два разных вопроса

Разделение, которое экономит много споров:

  1. «Сейчас плохо?» — вопрос про состояние. Ответ нужен всегда, быстро, дёшево, круглосуточно, с гарантией доставки. Это симптомная телеметрия: она отслеживает то, что чувствует пользователь.
  2. «Почему плохо?» — вопрос про причину. Ответ нужен изредка, только во время расследования, зато подробный и в разрезе, который заранее не был известен.

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

Слово «observability» (наблюдаемость) в маркетинге означает «наш продукт дороже». В инженерном смысле определение полезное и проверяемое: система наблюдаема, если по её внешним выходам можно ответить на вопрос, который вы не задавали заранее, не выкатывая новый код. Тест простой: пришла жалоба «у клиентов из Казахстана с Android медленно оформляется заказ» — можете ли вы проверить это за десять минут по уже собранным данным? Если нужно добавить метку и подождать деплоя, система ненаблюдаема в этом разрезе, и это факт, а не оценка.

Четыре сигнала и что каждый умеет

Сигнал Единица данных Отвечает на вопрос Кардинальность Стоимость Типичный срок хранения
Метрики число во времени с набором меток «сколько сейчас и как менялось» низкая, известна заранее очень низкая на запрос 13–15 мес. после прореживания
Логи текстовая или структурированная запись события «что именно произошло в этом месте кода» любая высокая, растёт с трафиком 7–30 дней горячих
Трейсы дерево спанов одного запроса «где ушло время и кто кого звал» любая высокая, режется сэмплированием 3–14 дней
Профили распределение ресурса по стекам вызовов «на чём именно занят процессор или память» средняя средняя, непрерывный сбор 7–30 дней

Пятый сигнал, формально не сигнал, но по практической ценности во время инцидента обгоняющий остальные, — журнал изменений: деплои, миграции, переключения фичефлагов, изменения конфигурации, работы провайдера. Большинство инцидентов начинается с изменения, и первый вопрос дежурного — «что поменялось за последний час»; если ответ не выводится на тот же экран, что и графики, вы потеряете двадцать минут на переписку. Стоит это почти ноль — аннотации на графике по событию из CI (стратегии релиза, а безопасные выкатки — глава 13).

Правило, которое реально решает, что куда класть

Формулировка, которая работает лучше всех таксономий:

Метрики отвечают на вопросы, которые вы знали заранее. Логи и трейсы — на те, которых не знали.

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

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

Метрики: арифметика кардинальности

Временной ряд в системах вроде Prometheus — это уникальная комбинация имени метрики и всех значений меток. Не метрика — ряд. Строка http_requests_total{service="orders", route="/checkout", method="POST", code="200"} — один ряд. Поменяйте любое значение — другой ряд, отдельный кусок памяти, отдельная запись в индексе.

Посчитаем на реальном порядке величин: 12 сервисов, в среднем по 25 маршрутов, 2 метода на маршрут, 5 различимых классов кода ответа.

  • service — 12 рядов;
  • + route — 12 × 25 = 300;
  • + method — 600;
  • + code3 000.

Три тысячи рядов — это ничто, любой ноутбук потянет. Дальше начинается интересное.

  • Добавили pod (мы же хотим видеть, какой инстанс тормозит), 60 подов: 3 000 × 60 = 180 000.
  • Латентность считаем гистограммой с 12 бакетами. Гистограмма Prometheus — это 12 рядов _bucket плюс +Inf, плюс _sum, плюс _count = 15 рядов вместо одного; возьмём 14 как среднее по метрикам с меньшим числом бакетов: 180 000 × 14 = 2 520 000.
  • И кто-то добавляет user_id, потому что «полезно же»: 2 520 000 × 50 000 = 1,26 × 10¹¹.

Рост числа временных рядов при добавлении меток

Последнее число не «много». Оно физически невозможно, и вот почему.

Память. Эмпирическое правило для Prometheus — от 2 до 8 КБ оперативной памяти на активный ряд (голова TSDB, индекс, незакрытый чанк), зависит от версии и от того, сколько рядов вы одновременно трогаете запросами. При 4 КБ: 3 000 рядов → 12 МБ (незаметно); 180 000 → 720 МБ (уже надо думать); 2 520 000 → около 10 ГБ только на активные ряды, без памяти под выполнение запросов, то есть отдельная машина с 32 ГБ на грани.

Диск. Prometheus после сжатия хранит примерно 1,3–2 байта на сэмпл. При интервале сбора 15 секунд ряд даёт 4 сэмпла в минуту, то есть 5 760 в сутки. Для 2,52 млн рядов при 2 байтах:

2 520 000 × 5 760 × 2 Б = 29,0 ГБ в сутки ≈ 871 ГБ в месяц

При горячем хранении 15 дней это 435 ГБ на диске — терпимо, но растёт линейно вместе с кардинальностью, без пощады.

Привычка, которая всё это предотвращает: прежде чем добавить метку, умножить в уме. Метка на 60 значений — это ×60 к памяти, диску, времени выполнения запросов и счёту от вендора; иногда оно того стоит (pod часто стоит). Метка на 50 000 значений не стоит никогда.

Где здесь организационная граница. Убрать метку технически — одна строка. Организационно — вы отнимаете разрез у команды, которая на него смотрит раз в квартал, но в тот раз он спас ей полдня. Аргумент «это стоит нам 10 ГБ памяти» проигрывает аргументу «мне это было нужно», если у затрат нет владельца. Работает только явное правило: у каждой метрики есть владелец и потребитель (алерт, панель SLO или регулярный отчёт), а ряд без потребителя удаляется по ревизии. Правило должно быть записано и согласовано до, а не во время спора.

Типы метрик и что с ними нельзя делать

Counter — монотонно растущий счётчик. Абсолютное значение бессмысленно, смотреть надо на производную: rate() или increase(). Счётчик переживает рестарт процесса сбросом в ноль, и корректная функция расчёта скорости это обнаруживает; поэтому счётчики никогда не «уменьшают» — убывание означает рестарт, а не бизнес-событие.

Gauge — мгновенное значение: длина очереди, открытые соединения, память. Опасность в том, что между двумя измерениями с интервалом 15 секунд может произойти что угодно: пик очереди длиной 3 секунды виден с вероятностью около 20 %. Если пики важны — считайте их счётчиком превышений порога, а не gauge.

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

Почему нельзя усреднять перцентили

Это не педантизм, это ошибка в разы. Два инстанса за одно окно:

  • A: 10 000 запросов, из них 9 800 по 10 мс и 200 по 800 мс. Сортируем: позиции 1–9 800 — 10 мс, позиции 9 801–10 000 — 800 мс. Позиция p99 — 9 900-я, попадает в хвост. p99(A) = 800 мс.
  • B: 10 000 запросов, все ровно по 20 мс. p99(B) = 20 мс.

Дашборд усредняет: (800 + 20) / 2 = 410 мс. Красиво, тревожно, неправильно. Истинный p99 объединённого потока: всего 20 000 запросов, позиция p99 — 19 800-я; отсортированный ряд — 9 800 значений по 10 мс (позиции 1–9 800), затем 10 000 по 20 мс (позиции 9 801–19 800), затем 200 по 800 мс. Позиция 19 800 — это 20 мс, то есть истинный p99 = 20 мс.

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

# Верно: складываем счётчики бакетов, потом оцениваем квантиль
histogram_quantile(
  0.99,
  sum by (le) (rate(http_request_duration_seconds_bucket{job="checkout"}[5m]))
)

# Неверно: сначала квантиль по каждому инстансу, потом среднее
avg(histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{job="checkout"}[5m])))

То же касается усреднения по времени: «средний p99 за месяц» — величина без интерпретации. Про то, почему усреднение по времени вообще опасно при разговоре о доступности, — ниже, в разделе про бюджет.

Гистограммы: что именно возвращает квантиль

Гистограмма не хранит значения, она хранит счётчики «сколько наблюдений было не больше, чем le», а квантиль восстанавливается интерполяцией внутри бакета, в который попала нужная позиция. Отсюда конкретная арифметика. Пусть за окно 10 000 запросов, накопленные счётчики:

граница le накоплено
0,1 9 700
0,25 9 850
0,5 9 990
1 10 000

Позиция p99 — 9 900-я. Она между le=0,25 (9 850) и le=0,5 (9 990). В бакете (0,25; 0,5] — 140 наблюдений, нам нужно 50-е из них. Линейная интерполяция:

0,25 + (9 900 − 9 850) / 140 × (0,5 − 0,25) = 0,25 + 0,357 × 0,25 = 0,339 с

Границы бакетов, истинный p99 и оценка histogram_quantile

Формула предполагает равномерное распределение внутри бакета. В реальности задержки обычно сгрудились у левого края: если все 140 наблюдений лежат около 0,26 с, истинный p99 — 0,26 с при оценке 0,339 с, ошибка 30 %; лежали бы у 0,49 — оценка была бы занижена. Что из этого следует практически:

  1. Значение histogram_quantile — утверждение «p99 лежит внутри бакета», а не число. Точность равна ширине бакета, и знаки после запятой этого не меняют.
  2. Границы бакетов выбираются под пороги SLO, а не по умолчанию. Если SLO обещает «быстрее 300 мс», в наборе обязана быть граница 0,3: тогда проверка перестаёт зависеть от интерполяции — доля считается делением счётчиков, точно.
  3. Дефолтный набор Prometheus (.005 .01 .025 .05 .1 .25 .5 1 2.5 5 10) не подходит почти никому: он про сервис с задержками в десятки миллисекунд и разваливается на медленных ручках.
# Точная доля запросов быстрее порога SLO — без интерполяции,
# потому что граница 0.3 явно есть в наборе бакетов
sum(rate(http_request_duration_seconds_bucket{job="checkout", le="0.3"}[5m]))
  /
sum(rate(http_request_duration_seconds_count{job="checkout"}[5m]))

Стоит знать про нативные гистограммы Prometheus: вместо фиксированных границ — экспоненциальная сетка с настраиваемым разрешением, хранится в одном ряду вместо пятнадцати, что снимает и проблему выбора бакетов, и часть проблемы кардинальности (документация).

И ещё одна систематическая ошибка, о которой в контексте мониторинга забывают, — coordinated omission: если замеряющий клиент блокируется в ожидании ответа, он перестаёт слать запросы во время замедления, и самые плохие замеры просто не появляются, а распределение выходит оптимистичным на порядок. Классический разбор — доклад Гила Тене «How NOT to Measure Latency»; про корректные замеры — «Измерения» и нагрузочное тестирование.

Каркасы: золотые сигналы, RED, USE

Три ходовых набора, отвечающих на вопрос «с чего начать инструментирование».

  • Четыре золотых сигнала (Google SRE): задержка, трафик, доля ошибок, насыщение. Деталь, которую все теряют: задержку успешных и неуспешных запросов надо считать раздельно — быстрый 500-й ответ прекрасно улучшает общий p99 и маскирует аварию.
  • RED (Rate, Errors, Duration) — для сервисов, обрабатывающих запросы: три метрики на сервис, одинаковые у всех, отсюда единый дашборд для всего парка (формулировка Тома Уилкинса).
  • USE (Utilization, Saturation, Errors) — для ресурсов: CPU, диск, пул соединений, очередь (метод Брендана Грегга). USE отвечает «что упёрлось», RED — «кому от этого плохо».

Честная оговорка: каркас — это чеклист покрытия, а не выбор SLI. RED даст долю ошибок по HTTP-коду; является ли она тем, что чувствует пользователь, каркас не знает — сервис, честно отдающий 200 OK с пустым списком товаров из-за упавшего поиска, по RED идеально здоров. Выбор показателя — работа из главы 02. Отдельно стоит насыщение: это единственный сигнал, который опережает аварию (загрузка пула 85 % ещё не даёт ошибок, но говорит, что запас кончается), и превращается он в планирование ёмкости.

Какой сигнал брать под какой вопрос

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

Логи: дороже, чем кажется

Считаем на том же порядке величин: сервис держит 10 000 запросов в секунду, одна строка access-лога в структурированном виде — примерно 600 байт (метки, идентификаторы, путь, коды, тайминги).

10 000 × 600 Б = 6 МБ/с = 518 ГБ в сутки

И это только access-лог. Прикладные логи обычно дают ещё в 3–5 раз больше строк, то есть реалистично 1,5–2,5 ТБ в сутки сырых данных; после сжатия и с учётом индексов на диске остаётся примерно того же порядка, а хранение 30 дней — это 45–75 ТБ. Такой кластер стоит уже не как «часть инфраструктуры», а как отдельная статья бюджета, часто сопоставимая со стоимостью самого сервиса.

Отсюда практические решения, каждое со своей платой:

  • Структурированные логи (JSON) вместо текста. Плата — читаемость глазами в терминале, выигрыш — фильтрация по полю вместо регулярных выражений. На объёмах больше пары гигабайт в сутки альтернативы нет.
  • Разные сроки хранения по классам: access 30 дней, debug 3 дня, аудит и события безопасности — по требованиям регулятора, отдельно и неизменяемо («Приватность и соответствие требованиям»).
  • Сэмплирование логов не «каждая десятая строка», а по классам: все ошибки, все медленные, 1 % успешных. Один из немногих приёмов, снижающих счёт в разы без потери диагностической ценности.
  • Никаких персональных данных и секретов в логах — не из благочестия: лог живёт дольше и копируется шире, чем база, и утекает вместе с доступом к системе логирования («Управление секретами»).
{"ts":"2026-07-16T10:04:12.318Z","level":"error","service":"checkout",
 "trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7",
 "route":"/api/orders","order_id":"ord_81f3","user_id":"u_44921",
 "err":"payment_gateway_timeout","upstream":"billing","latency_ms":3021,
 "attempt":2,"deploy":"checkout@2026.07.16-3"}

Здесь важны три поля: trace_id связывает строку с трейсом, deploy — с журналом изменений, attempt показывает, что это ретрай. Первое поле — то, что превращает логи из свалки в инструмент; без него связывание сигналов делается глазами по времени, а это работает только при трафике «десятки запросов в минуту».

Логи как участник каскада

Отдельный сюжет: логирование при аварии — усиливающая петля обратной связи. Механизм: сервис начинает ошибаться → на каждую ошибку пишется stack trace (в 20–50 раз длиннее обычной строки) → объём логов растёт в десятки раз → лог-агент упирается в CPU и сеть → сервису достаётся меньше ресурсов → ошибок становится больше. Плюс заполнение диска, после которого падает всё на хосте, включая то, что работало. Это ровно та структура, которую системное мышление называет усиливающей петлёй: следствие возвращается к причине с тем же знаком и разгоняет само себя («Петли обратной связи»; к каскадам в нашем треке — глава 11).

Что с этим делают: ограничение скорости записи логов на уровне приложения (rate limiting логгера по ключу сообщения), отдельный раздел диска под логи, жёсткий лимит CPU для агента и — обязательно — дедупликация повторяющихся сообщений (... и ещё 14 812 таких же за минуту).

Трейсы: единственный сигнал про причинность между сервисами

Трейс — дерево спанов одного запроса; спан — интервал с началом, концом, именем, атрибутами и ссылкой на родителя. Вместе они дают то, чего не даст ни метрика, ни лог: порядок и вложенность вызовов конкретного запроса, из которого видно, где именно из восьмисот миллисекунд ушло четыреста. Склеивается это контекстом, который передаётся между процессами: стандарт — W3C Trace Context, заголовок traceparent формата версия-trace_id-span_id-флаги. Ключевая часть работы — не собрать спаны, а не потерять контекст на каждой границе.

Три места разрыва на этой диаграмме встречаются постоянно:

  1. Клиент не начинает трейс. Всё, что происходит до входа в вашу сеть — DNS, TLS, мобильная сеть, — вне трейса: сервер честно рапортует 480 мс, пользователь ждёт 2,5 секунды.
  2. База данных не участвует в трейсе. SQL-протокол не переносит контекст; обходной путь — комментарий в запросе (/*traceparent=00-4bf9...-01*/ SELECT ...), который потом видно в pg_stat_activity и в медленном логе.
  3. Асинхронная граница. Очередь передаёт контекст, только если продюсер явно положил его в заголовки сообщения; пропустили — и вся асинхронная часть обработки превращается в обрывки без связи с запросом («Обмен сообщениями»).

Сэмплирование: арифметика

Объём. 10 000 запросов в секунду, 8 спанов на запрос, 400 байт на спан после сжатия:

10 000 × 8 × 400 Б = 32 МБ/с = 2,76 ТБ в сутки

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

Head sampling — решение принимается в начале запроса, случайно, с фиксированной вероятностью. При 1 % объём падает до 27,6 ГБ в сутки — прекрасно. Теперь считаем, что мы этим купили и что потеряли. Пришла жалоба: «у меня всё тормозит», за сессию клиент сделал 50 запросов. Вероятность, что хотя бы один попал в выборку:

1 − 0,99⁵⁰ = 1 − 0,605 = 0,395

Меньше 40 %. В шести случаях из десяти вы отвечаете «трейсов по вашей проблеме нет». При 0,1 %: 1 − 0,999⁵⁰ = 4,9 %, то есть практически никогда. При этом для агрегированной статистики того же 1 % более чем достаточно: при 10 000 rps это 8,64 млн трейсов в сутки, погрешность ничтожна. Head sampling хорош для «как в среднем» и бесполезен для «что случилось у вот этого».

Tail sampling — решение принимается после завершения трейса, когда известны длительность и статус: сохранять все трейсы с ошибками, все медленнее порога и 1 % остальных как фон. Плата: коллектор держит спаны в памяти до истечения окна ожидания и обязан получать все спаны одного трейса — нужна маршрутизация по trace_id, а это уже не тривиальная установка.

# OpenTelemetry Collector: сохраняем всё интересное и 1 % фона
processors:
  tail_sampling:
    decision_wait: 10s          # ждём завершения трейса
    num_traces: 100000          # сколько трейсов держим в памяти
    policies:
      - name: errors            # все ошибки — целиком
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow              # всё медленнее порога SLO
        type: latency
        latency: { threshold_ms: 300 }
      - name: baseline          # фон для статистики
        type: probabilistic
        probabilistic: { sampling_percentage: 1 }

Оценим объём такой политики: пусть ошибок 0,3 % запросов, медленных ещё 1 %, фон 1 % — итого около 2,3 % против 100 %, то есть 63 ГБ в сутки вместо 2,76 ТБ при полном покрытии проблемных запросов. Разница между этим и наивным head sampling — не в деньгах, а в том, отвечает ли система на вопросы во время инцидента (обзор подходов).

Связывание сигналов

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

  • Единые имена меток во всех сигналах: service, env, version называются одинаково в метрике, логе и спане. Для этого есть семантические соглашения OpenTelemetry, и брать их, а не изобретать свои, стоит хотя бы потому, что готовые дашборды рассчитаны на них.
  • trace_id в каждой строке лога — реализуется один раз в middleware; аннотации деплоев на всех графиках — дёшево и окупается на первом же инциденте.
  • Exemplars в метриках: к бакету гистограммы прикрепляется trace_id запроса, попавшего в этот бакет. Вы видите на графике всплеск p99, кликаете по точке и попадаете в трейс запроса, который его создал, — единственный способ перейти от агрегата к примеру за один шаг, а не поиском по времени.

Где мерить: сервер, клиент, синтетика

Серверная метрика систематически оптимистичнее реальности, и разрыв надо оценивать, а не игнорировать. Пусть сервер отдаёт 0,05 % ошибок — красивая цифра, 99,95 %. Но на пути пользователя есть ещё DNS, TLS, мобильная сеть с обрывами, CDN и клиентский код; допустим, там теряется ещё 0,4 % сессий. Тогда пользователь видит:

100 % − 0,05 % − 0,4 % ≈ 99,55 %

В минутах за 30-дневный месяц (43 200 минут): вместо обещанных 43,2 минуты недоступности (99,9 %) пользователи получают 43 200 × 0,0045 = 194 минуты. Больше трёх часов, из которых на серверных графиках видно 22 минуты. Разрыв в девять раз, и это не гипербола — типичная картина для мобильных клиентов.

Что закрывает разрыв: RUM (real user monitoring) — телеметрия из браузера или приложения (реальное время до первого байта, ошибки сети и JS), единственный источник правды про ощущения пользователя; и синтетические (blackbox) пробы — регулярный запрос по критическому сценарию извне вашей сети. Пробы отвечают на вопрос «а вообще снаружи открывается» и работают, когда сервис лежит настолько, что метрик от него нет вовсе: «данных нет» и «всё хорошо» — разные состояния, различать их умеет только внешний наблюдатель. У замеров на клиенте своя цена: их объём пропорционален числу пользователей, а не серверов, и качество сети вы не контролируете; практический компромисс — сэмплировать RUM агрессивно (1–5 %) и считать по нему распределения, а не абсолютные значения.

Мониторинг и бюджет ошибок: сколько минут съедает обнаружение

Здесь самая недооценённая арифметика главы. Бюджет ошибок за 30-дневный месяц (43 200 минут):

SLO Доля бюджета Бюджет в месяц
99 % 1 % 432 минуты (7 ч 12 мин)
99,5 % 0,5 % 216 минут
99,9 % 0,1 % 43,2 минуты
99,95 % 0,05 % 21,6 минуты
99,99 % 0,01 % 4,32 минуты
99,999 % 0,001 % 25,9 секунды

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

Этап Что определяет Реалистично
Сбор метрики интервал scrape 15–60 с
Условие алерта окно for, чтобы не шуметь 2–5 мин
Доставка уведомления группировка в Alertmanager, пуш 30–90 с
Человек проснулся и открыл ноутбук ночь, сон 3–10 мин
Диагностика и митигация качество телеметрии, наличие готовой ручки 7–50 мин

Сложим только то, что происходит до начала работы: 30 с + 3 мин + 1 мин + 5 мин ≈ 9,5 минуты, и это оптимистичный сценарий — быстрый сбор, короткое окно, дежурный проснулся за пять минут. Сопоставим с бюджетом:

  • 99,9 % (43,2 мин/мес.): одно ночное срабатывание съедает 22 % месячного бюджета до того, как кто-то начал что-то делать. Три инцидента за месяц — и две трети бюджета потрачены на просыпание. Вывод: на этом уровне человек в петле ещё помещается, но запас крайне мал.
  • 99,99 % (4,32 мин/мес.): обнаружение и просыпание вдвое превышают весь месячный бюджет. Это математический факт, а не вопрос дисциплины: четыре девятки недостижимы при участии человека в устранении. Требуется автоматическая митигация — автоматический откат по метрике, автоматический вывод инстанса из балансировки, переключение трафика без ручного решения.
  • 99,999 % (26 секунд в месяц): дежурный не успеет прочитать уведомление. Это уровень, на котором любая деградация должна маскироваться самой системой, что означает избыточность в нескольких регионах, автоматический failover, тестирование этого failover’а и штат, который всё это поддерживает. Стоимость растёт не линейно: каждая девятка требует убрать следующий по значимости класс отказов, а он всегда дороже предыдущего.

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

Что из этого следует для мониторинга конкретно. Время обнаружения — инженерный параметр, которым можно управлять, и он покупается за деньги: уменьшить интервал сбора с 60 до 15 секунд — ×4 к объёму данных; сократить окно for с 5 до 2 минут — рост числа ложных срабатываний, а с ним усталость дежурного (глава 05); ввести алерты по скорости прожигания бюджета (burn rate) — сложнее в настройке, зато реагируют на серьёзную аварию за минуты, а на мелкую утечку — за часы.

# Скорость прожигания бюджета: во сколько раз быстрее нормы тратим.
# Для SLO 99,9 % допустимая доля ошибок — 0.001. Значение 1 — тратим ровно
# по плану; 14,4 — весь месячный бюджет сгорит за двое суток.
(
  sum(rate(http_requests_total{job="checkout", code=~"5.."}[5m]))
    /
  sum(rate(http_requests_total{job="checkout"}[5m]))
) / 0.001

Что показывать: дашборды, которые открывают

Дашборд — интерфейс, у него есть пользователь и сценарий; смешивать сценарии — верный способ получить панель, на которую никто не смотрит.

  • Уровень 1: состояние SLO. Один экран на весь продукт: для каждого пользовательского сценария — значение SLI, остаток бюджета ошибок, скорость прожигания. Открывается на приёмке смены и на еженедельном разборе. 5–10 чисел, ноль графиков «на всякий случай».
  • Уровень 2: сервис. RED плюс насыщение ресурсов плюс зависимости, одинаковый по форме для всех сервисов и сгенерированный из шаблона. Открывается дежурным в первые две минуты инцидента, чтобы локализовать.
  • Уровень 3: отладка. Всё остальное: живёт рядом с кодом сервиса, поддерживается командой-владельцем, может быть каким угодно.

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

Состояние «Сирота» — не теоретическое. В любой системе старше двух лет большая часть рядов и панелей находится именно в нём, и именно там лежит основной резерв экономии. Проблема в том, что переход «Сирота → Удалена» требует не технического решения, а согласия: кто-то должен взять на себя риск, что через месяц удалённое понадобится. Без явного правила («ревизия раз в квартал, порог 90 дней без обращений, восстановление по запросу») этот переход не происходит никогда.

Цена мониторинга

Цена складывается из трёх слагаемых, и обычно считают только первое. Деньги — хранение, трафик, лицензии, ресурсы под коллекторы и агенты. Модели тарификации у вендоров разные — за хост, за принятый гигабайт, за пользовательскую метрику, за индексированное поле, — и объединяет их одно свойство: счёт растёт вместе с трафиком сервиса, а не с полезностью данных. Порядок, к которому приходят команды, честно посчитавшие: телеметрия обходится в 10–30 % инфраструктурного счёта. Цифра большая, и её почти никогда не выносят в отдельную строку бюджета, из-за чего никто ей не управляет.

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

Внимание. Каждый график и каждый ряд — то, во что кто-то будет всматриваться в три часа ночи; плотность полезного сигнала важнее полноты. То же и с алертами: сигнал, на который нет действия, обесценивает все остальные, потому что учит игнорировать их скопом (глава 05, человеческая цена дежурства — глава 06).

Где инженерное упирается в организационное. Инженер видит, что кардинальность выросла в пятьдесят раз, и знает, какую метку убрать. Он не может её убрать: метка нужна другой команде, счёт приходит третьей, решение принимает четвёртая. Технического решения у этой задачи нет. Работающие организационные — из тех, что не требуют героизма: выделенная строка бюджета на телеметрию с владельцем; квота на количество рядов на команду (превысил — сам решаешь, что убрать); ежеквартальная ревизия сирот с правом восстановления. Всё скучно, всё работает, и всё нужно вводить до того, как счёт вырастет вдвое, — после придётся резать в спешке и не то.

Мониторинг должен переживать то, что он мониторит

Четыре требования, каждое из которых написано чьей-то аварией.

  1. Мониторинг не живёт внутри наблюдаемой системы. Grafana в том же кластере Kubernetes, что и продакшн, — это Grafana, недоступная ровно тогда, когда нужна. То же про уведомления: если алерты идут через сервис, который лежит вместе с вами, они не придут.
  2. Отсутствие данных — состояние, требующее алерта, иначе полностью упавший сервис выглядит идеально здоровым: ошибок ноль, потому что запросов ноль. Плюс dead man’s switch — алерт, который обязан срабатывать всегда, и его молчание означает поломку самого мониторинга.
  3. Внешняя проба независимого происхождения — пусть примитивная, из другого облака или с чужого VPS.
  4. Деградация вместо отказа: агент, не сумевший отправить данные, обязан их отбросить, а не копить в памяти до OOM приложения (глава 10). А единственный способ узнать, что всё это работает в аварии, — устроить аварию (глава 12).

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

Книга Site Reliability Engineering полезна, и глава про мониторинг распределённых систем в ней действительно хорошая. Но её написали люди, у которых была собственная TSDB (Borgmon, затем Monarch), выделенные команды на инструментарий и парк, где эффект масштаба меняет экономику. Полезно разделить, что из этого — идеи, а что — следствия ресурсов.

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

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

Практика: минимальный набор для команды из пяти человек

Порядок внедрения по убыванию отношения пользы к затратам:

  1. Blackbox-проба критического сценария извне плюс уведомление в мессенджер. Полчаса работы, закрывает случай «лежит всё».
  2. RED-метрики на каждый сервис: счётчик запросов с метками route, method, code и гистограмма длительности с бакетами под пороги SLO.
  3. Структурированные логи с trace_id, уровень info в проде, debug включается флагом на время.
  4. Один дашборд SLO на весь продукт, по одному типовому на сервис из общего шаблона, аннотации деплоев на графиках.
  5. Трейсы — когда сервисов станет больше четырёх и вопрос «кто из них тормозит» перестанет решаться взглядом.
from prometheus_client import Counter, Histogram

# Метки только низкокардинальные. route — ШАБЛОН маршрута ("/orders/{id}"),
# а не конкретный путь ("/orders/8891"): иначе каждый заказ породит свой ряд.
REQUESTS = Counter(
    "http_requests_total", "Всего обработанных HTTP-запросов",
    ["route", "method", "code"],
)

# Границы бакетов подобраны под пороги SLO: 0.3 (основной) и 1.0 (жёсткий).
# Обе обязаны присутствовать явно, иначе долю быстрее порога придётся
# оценивать интерполяцией — то есть неточно.
LATENCY = Histogram(
    "http_request_duration_seconds", "Длительность обработки запроса",
    ["route", "method"],
    buckets=(0.02, 0.05, 0.1, 0.2, 0.3, 0.5, 0.8, 1.0, 2.0, 5.0),
)


async def metrics_middleware(request, handler):
    """Одно место, где инструментируются все ручки сервиса."""
    route = request.match_info.route.resource.canonical  # шаблон, не сырой путь
    with LATENCY.labels(route=route, method=request.method).time():
        try:
            response = await handler(request)
            code = response.status
        except Exception:
            code = 500
            raise
        finally:
            # код ответа приводим к классу: 2xx/3xx/4xx/5xx вместо 43 значений
            REQUESTS.labels(route=route, method=request.method,
                            code=f"{code // 100}xx").inc()
    return response

Две детали здесь важнее остального. Первая — шаблон маршрута вместо сырого пути: /orders/8891 в метке означает столько рядов, сколько было заказов, то есть неограниченный рост, и это самая частая причина взрыва кардинальности на практике. Вторая — схлопывание кода ответа в класс: различать 200 и 204 в метрике почти никогда не нужно, а рядов это удваивает; если конкретный код понадобится, он есть в логе. Стоимость такой инструментации: 12 сервисов × 25 маршрутов × 2 метода × 4 класса кода = 2 400 рядов на счётчик и 12 × 25 × 2 × 12 = 7 200 на гистограмму — меньше 10 000 рядов, около 40 МБ памяти. Практически бесплатно, и при этом достаточно, чтобы считать SLO по доступности и по задержке.

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

  • Метка с неограниченным множеством значений: user_id, order_id, сырой путь, текст ошибки, IP-адрес. Первая причина неработающего мониторинга.
  • Усреднение перцентилей по инстансам или по времени: ошибка в разы в обе стороны.
  • Бакеты гистограммы по умолчанию при том, что пороги SLO лежат между ними: проверить SLO становится невозможно.
  • Алерты на причины вместо симптомов: «загрузка CPU 90 %» будит человека, когда пользователям хорошо, и молчит, когда им плохо по другой причине.
  • Задержка успешных и неуспешных запросов в одной гистограмме. Быстрые ошибки улучшают график ровно в момент аварии.
  • Мониторинг внутри наблюдаемой системы плюс отсутствие алерта на отсутствие данных: система отказывает синхронно с наблюдаемой, а мёртвый сервис выглядит здоровым.
  • Дашборды и метрики без владельца. Растут неограниченно, во время инцидента мешают искать нужное.
  • Head sampling трейсов при разборе единичных жалоб (шанс менее 40 %) и измерение только на сервере (систематически завышенная доступность).

Мини-итог

  • Мониторинг решает две разные задачи — «сейчас плохо?» и «почему плохо?». Первая требует простоты и надёжности, вторая — богатства и глубины; одним инструментом обе не закрываются.
  • Метрики отвечают на вопросы, известные заранее; логи и трейсы — на неизвестные. Низкокардинальные разрезы в метрики, идентификаторы — в логи и трейсы: одна метка на 50 000 значений превращает три тысячи рядов в сотню миллиардов.
  • Перцентили не складываются и не усредняются: складываются бакеты, квантиль считается один раз от суммы. И сам квантиль по гистограмме — утверждение про бакет, а не число, поэтому границы подбираются под пороги SLO.
  • Head sampling трейсов даёт статистику и не даёт ответа по конкретной жалобе: 1 % и 50 запросов клиента — меньше 40 % шанса. Tail sampling дороже в установке и решает эту задачу.
  • Обнаружение и просыпание съедают около 9–10 минут. При SLO 99,9 % (43 минуты в месяц) это четверть бюджета на инцидент; при 99,99 % (4,3 минуты) человек в петле не помещается математически. Пять девяток — про полную автоматику и другой класс затрат, а не про старательность.
  • Серверные метрики систематически оптимистичны: разрыв с клиентской реальностью бывает в разы и закрывается RUM и внешними пробами.
  • Телеметрия стоит 10–30 % инфраструктурного счёта. Без выделенного владельца этой строкой никто не управляет, а большая часть объёма приходится на данные, которые никто не читает.
  • Мониторинг обязан переживать наблюдаемую систему: отдельная площадка, алерт на отсутствие данных, dead man’s switch, внешняя проба.

Источники

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

Что дальше

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

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

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

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

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

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