DarkRiDDeR14 мин

Метрики приложения: начинаем с одного операционного вопроса

НаблюдаемостьPrometheusПрактика

Симптом знакомый: на графике есть общее число HTTP-запросов, а ответить на вопрос «в какой операции появились ошибки?» всё равно нельзя. Цена такой метрики проявляется во время разбора. Инженер сравнивает несвязанные пики, добавляет ещё одну линию или начинает искать проблему по логам без точки входа. Если в название метрики положить всё подряд, следующий шаг становится ещё дороже: график растёт, а причина по-прежнему не отделена от фонового шума.

В августе 2020 года я бы не начинал с набора дашбордов и не называл это готовой observability-платформой. Достаточно выбрать один повторяемый вопрос для HTTP-операции: «появились ли в учебном окне ошибки завершения checkout?» Ниже только контролируемая фикстура с выдуманными значениями. Она показывает форму сигнала, labels, запрос и порог; ни Prometheus server, ни реальные нагрузки, ни production-alert в этом материале не запускались.

Сначала формулируем вопрос, потом имя метрики

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

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

Малый контракт учебного сигнала: каждая строка отвечает на один вопрос
ВеличинаТип и единицаРазрешённые labelsОперационный вопросНе доказывает
store_http_requests_totalcounter, завершённые запросыoperation: 3 имени; outcome: success/errorВ какой операции и с каким исходом завершилась работа?почему произошла ошибка и что видел браузер
store_http_request_duration_secondsучёт длительностей, secondsoperation: 3 имениЕсть ли данные о длительности выбранной операции?конкретную причину медленного запроса
Учебный порог >= 2условие над counter за 10 минуттолько checkout/errorПересекла ли контролируемая серия границу упражнения?production-порог, SLO или допустимую нагрузку

В названии есть доменная часть store, сущность http_requests и суффикс _total для накапливаемого счётчика. Это не косметика: из имени видно, что значение не является миллисекундами или текущим числом подключений. Для длительности используем seconds, а не смешиваем миллисекунды в одном месте и секунды в другом. Официальные рекомендации Prometheus предлагают одну величину и одну единицу на имя; это делает запросы и ревью понятнее даже без готового dashboard.

Выбираем тип по форме состояния

Counter растёт на каждое завершение и может обнулиться при перезапуске процесса. Он подходит для числа запросов и ошибок, но сам по себе редко отвечает на вопрос «что происходило за последние пять минут». Для окна используют функцию над изменением counter, например increase() или rate(); в этой статье нужен именно count ошибок в контролируемом окне, поэтому выбираю increase(). Gauge меняется в обе стороны и здесь не подходит: «ошибки прямо сейчас» не являются состоянием, которое приложение должно произвольно выставлять в ноль.

Для длительности нужен не один усреднённый number на всю систему, а набор наблюдений. Клиентская библиотека может экспортировать histogram или summary; выбор зависит от задачи и версии библиотеки. В первом упражнении я не строю p95, не сравниваю SLO и не объявляю latency-контракт. Достаточно сохранить seconds и стабильное имя операции, чтобы следующая проверка могла сравнить один и тот же вход. Если операции не ограничены словарём, сначала надо договориться о словаре, а не добавлять route или произвольный URL в label.

Контролируемая фикстура вместо обещания реального графика

Ниже находится исполняемый фрагмент из этого revision-модуля. Он принимает четыре учебных события, разрешает только три операции и два исхода, затем печатает текстовую exposition с counter и суммой/количеством длительностей. В нём нет сетевого вызова, метрики не отправляются в Pushgateway и нет зависимости от конкретной client library. Именно поэтому пример можно проверить как контракт имён и labels, не выдавая его за снятый с production endpoint результат.

// Учебная фикстура из этого revision-модуля. Не подключена к Prometheus server.
const events = [
  { operation: 'catalog', outcome: 'success', durationSeconds: 0.03 },
  { operation: 'checkout', outcome: 'success', durationSeconds: 0.08 },
  { operation: 'checkout', outcome: 'error', durationSeconds: 0.11 },
];

// Функция проверяет allowlist и рендерит только два bounded label:
const exposition = renderFixtureExposition(events);
console.log(exposition);

# TYPE store_http_requests_total counter
store_http_requests_total{operation="checkout",outcome="error"} 1
store_http_request_duration_seconds_count{operation="checkout"} 2

В примере специально нет user_id, email, полного URL, request ID, текста исключения или IP-адреса. Эти значения помогают найти один случай в журнале, но почти никогда не являются ограниченной размерностью метрики. У operation есть короткий allowlist, у outcome — два допустимых значения. Если операция неизвестна, фикстура завершается ошибкой. Это лучше, чем незаметно породить новую series из имени нового endpoint-а или строки, пришедшей из запроса.

Вертикальная схема выбора метрики: сначала один вопрос про завершённую HTTP-операцию, затем counter с operation и outcome, отдельно duration в seconds, ограниченный учебный запрос и действие после его результата
Схема отделяет сигнал от диагноза: counter показывает ветку для расследования, но не объясняет источник ошибки без следующей проверки.

Порог — это граница решения, а не украшение графика

Порог имеет смысл только вместе с действием. Для контролируемой серии вопрос звучит так: «если за десять минут fixture получила две или больше ошибок checkout, надо ли открыть один журнал или повторить изолированный сценарий?» Число 2 здесь выбрано для упражнения: одна ошибка проверяет, что счётчик и label существуют, две — что условие меняет ветку. Оно не выводится из пользовательского трафика, не описывает допустимый процент ошибок и не должно копироваться в alert rule.

PromQL-выражение ниже суммирует изменение одного counter в окне. В реальном Prometheus counter reset и фактические точки scrape обрабатываются механизмом самого движка; in-memory fixture ниже проверяет только прозрачную арифметику двух snapshots. Это важное различие. Нельзя сказать «локальный delta равен результату production PromQL» и пропустить проверку на выбранной версии сервера. Но можно сначала договориться, какой именно label и какое пересечение должно поменять следующее действие.

# Учебный PromQL-вопрос, а не production-alert:
# «В контролируемом десятиминутном окне стало две или больше ошибок checkout?»
sum(increase(store_http_requests_total{
  operation="checkout",
  outcome="error"
}[10m])) >= 2

# В in-memory fixture вместо PromQL проверяется та же простая граница:
errorDelta(controlledSamples) >= 2 // true
errorDelta(oneErrorSamples) >= 2  // false

Короткий маршрут первой метрики

  1. Записать один вопрос в форме «операция, исход, окно, следующее действие». Не начинать с имени dashboard или общей фразы «нужны метрики».
  2. Выбрать момент завершения операции и определить, что считается success и error. Если исход ещё не известен, не увеличивать окончательный counter раньше времени.
  3. Составить allowlist labels. Для этого примера оставить только operation и outcome; отдельно записать, где живут request ID и текст ошибки.
  4. Назвать metric одной величиной и одной единицей: counter получает _total, длительность хранится в seconds. Проверить название по документации Prometheus до добавления в код.
  5. Прогнать контролируемые события и убедиться, что неизвестная operation отвергается, а строка exposition не содержит user-specific labels.
  6. Сформулировать учебный запрос и порог, затем записать действие по обе стороны границы. Перед реальным alert отдельно проверить scrape interval, reset, нагрузку и владельца реакции.

Граница первой проверки

После этого шага у проекта не появляется полный мониторинг. Нет данных о клиентах, сетевых hop-ах, базе, очереди, релизе или фактической нагрузке. Нет также SLO, error budget и обещания, что две ошибки одинаково важны для любого продукта. Это нормально для первой метрики: она должна сделать один вопрос проверяемым, а не создать видимость знания о всей системе. Если counter пересекает учебную границу, следующий артефакт — один связанный запрос или журнал, а не бесконечное добавление labels.

Числа, длительности, операции и окно в тексте учебные. Реальный порог выбирают после того, как есть согласованный смысл ошибки, период наблюдения, известная стоимость ложного срабатывания и возможность проверить контекст. В августе 2020 года автор только начинает связывать приложение с наблюдаемым сигналом: он умеет поставить маленький договор над кодом, но не приписывает себе опыт эксплуатации общей платформы или реальные production-значения.

Проверяемые источники

  • Prometheus: Metric and label naming — официальные правила: одна величина и единица на имя метрики, базовые единицы и отдельная осторожность с label dimensions
  • Prometheus: Instrumentation — официальные рекомендации для online-serving систем: считать завершённые запросы, ошибки и latency; не раздувать labels и держать счётчик ошибок рядом с числом попыток
  • Prometheus: Metric types — официальное описание counter, gauge, histogram и summary; выбор типа должен следовать форме наблюдаемой величины
  • Prometheus: Querying basics — официальная справка по selectors, range vectors, агрегации и выражениям PromQL; запросы ниже показаны как учебные формулировки