From 6ac0154da54b1007e4b59aef11a6ec65180fb2fd Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 22:04:14 +0300 Subject: [PATCH] editorial: refine metrics practice article 267 --- editorial/agent-rewrites/267.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editorial/agent-rewrites/267.json b/editorial/agent-rewrites/267.json index e98adb7..50d2307 100644 --- a/editorial/agent-rewrites/267.json +++ b/editorial/agent-rewrites/267.json @@ -1 +1 @@ -{"index":267,"slug":"editorial-2020-08-practice-metrics-basics","title":"Метрики приложения: как превратить сбой в проверяемый сигнал","excerpt":"Общий график запросов не отвечает, где возникла ошибка и что проверять дальше. Разбираем небольшой контракт для HTTP-операции: тип метрики, ограниченные labels, учебный запрос и явное действие после порога.","contentHtml":"

Симптом выглядит безобидно: график HTTP-запросов растёт, но из него нельзя понять, в какой операции появились ошибки. Команда открывает несколько дашбордов, сверяет несвязанные пики и вручную ищет нужные строки в логах. За это время растёт очередь разбора, задерживается релиз, а исправление опирается на догадки. Если в метрику добавить полный URL, пользователя и текст исключения, сигнал станет ещё дороже: Prometheus получит множество почти уникальных рядов, но причина сбоя не станет яснее.

Первая метрика должна отвечать на один операционный вопрос и вести к следующей проверке. Для HTTP-операции достаточно зафиксировать завершение, стабильное имя операции и исход success или error. Затем можно посчитать ошибки за окно и решить, открыть ли связанный лог или повторить сценарий. Такая метрика не заменяет трассировку, журнал и проверку клиента. Она сужает поиск и делает его воспроизводимым.

Сначала вопрос, потом имя

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

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

В этой статье разбирается только серверная HTTP-граница. Доступность браузера, DNS, сеть, база, очередь и ручное действие оператора остаются отдельными сигналами. Один counter не доказывает, что пользователь увидел корректный экран. Он показывает, что выбранная операция завершилась с указанным исходом.

Контракт сигнала для учебного сценария
ВеличинаТип и единицаLabelsВопросНе доказывает
store_http_requests_totalcounter, завершённые запросыoperation, outcomeКакая операция и с каким исходом завершилась?Причину ошибки и путь до пользователя
store_http_request_duration_secondshistogram или summary, секундыoperationКак распределяется длительность?Причину медленного ответа
Учебное условие >= 2increase() за 10 минутoperation=checkout, outcome=errorПересекла ли фикстура границу?Production-порог и SLO

Тип метрики следует из состояния

store_http_requests_total — накопительный счётчик. Он увеличивается при событии и может вернуться к нулю после перезапуска процесса. Сырое значение counter редко отвечает на вопрос о недавнем окне. Для количества событий за интервал применяют функцию над изменением счётчика. В учебном выражении ниже используется increase(); для скорости событий обычно применяют rate().

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

Для длительности храните секунды и собирайте распределение наблюдений. Histogram даёт buckets, сумму и количество наблюдений. Summary также считает сумму и количество, но его квантили имеют другую семантику и требуют отдельного выбора. В первом сигнале не нужно обещать p95 или SLO. Сначала договоритесь, что операция имеет стабильное имя и что единица времени одинакова везде.

Суффикс _total показывает накопительную природу counter. В имени длительности есть _seconds. Не смешивайте миллисекунды и секунды под одним именем. Иначе запросы будут синтаксически корректными, но сравнение значений станет ложным.

Labels должны отвечать на вопрос

Каждая уникальная комбинация имени метрики и labels образует отдельный time series. Поэтому operation должен брать значения из короткого словаря: например, catalog, checkout, profile. outcome может принимать два значения: success и error. Такой набор можно перечислить заранее и проверить в коде.

Не добавляйте в этот counter user_id, email, полный URL, request ID, текст исключения или произвольный статус из запроса. Эти данные нужны для поиска конкретного события. Поместите их в лог или трассу и свяжите записи через correlation ID. Label должен разделять агрегированные ветки, а не хранить историю отдельного пользователя.

Если значение label нельзя перечислить заранее, остановитесь и проверьте его смысл. Сколько рядов оно добавит? Какой запрос использует его? Можно ли нормализовать маршрут до шаблона, например /orders/:id, вместо полного URL с идентификатором заказа? Ответы должны быть в контракте до публикации метрики.

Учебный пример: четыре события и два snapshots

Ниже не клиент Prometheus и не результат нагрузки. Это учебный набор с выдуманными значениями. Он проверяет разрешённые labels, суммирование ошибок и поведение при неизвестной операции. В реальном сервисе инкремент выполняет выбранная библиотека, а результат нужно проверить на нужной версии сервера и с реальным интервалом scrape.

const allowedOperations = new Set(['catalog', 'checkout', 'profile']);<br>const allowedOutcomes = new Set(['success', 'error']);<br><br>const events = [{ operation: 'checkout', outcome: 'success', durationSeconds: 0.42 }, { operation: 'checkout', outcome: 'error', durationSeconds: 0.90 }, { operation: 'catalog', outcome: 'success', durationSeconds: 0.18 }, { operation: 'checkout', outcome: 'error', durationSeconds: 1.10 }];<br><br>for (const event of events) {<br>  if (!allowedOperations.has(event.operation)) throw new Error('unknown operation');<br>  if (!allowedOutcomes.has(event.outcome)) throw new Error('unknown outcome');<br>  if (event.durationSeconds &lt; 0) throw new Error('invalid duration');<br>}<br><br>// Учебный результат: checkout/error = 2.<br>// Это не production-данные и не готовый alert rule.

Проверка отбрасывает неизвестную операцию до публикации значения. Это отрицательный путь, и он важнее красивой строки exposition. Если новый endpoint молча создаёт label, дашборд может продолжить работать, а стоимость хранения и смысл агрегации изменятся незаметно. В настоящем коде ошибку нужно вернуть вызывающему слою или записать в отдельный технический сигнал.

\"Схема
Сигнал отделяет факт завершения операции от следующего шага расследования.

Запрос и порог

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

sum(increase(store_http_requests_total{ operation=\"checkout\", outcome=\"error\" }[10m])) >= 2

Выражение суммирует изменение counter за десять минут и проверяет условие. Число 2 выбрано только для упражнения: одна ошибка показывает, что label работает, две переводят пример в другую ветку. Оно не получено из трафика, не является допустимой долей ошибок и не должно копироваться в production alert.

Запрос зависит от точек scrape и от обработки reset counter. Локальная разность двух чисел в памяти не доказывает, что production PromQL вернёт такое же значение. При перезапуске процесса, задержке scrape или отсутствии ряда результат требует отдельной проверки. Нулевое значение также не всегда означает «ошибок не было»: ряд мог ещё не появиться.

Симптом → причина → проверка → действие

Диагностика первой метрики
СимптомПричинаПроверкаДействие
Есть общий рост, но не видно операцииНет bounded label operationПосмотреть series и словарь операцийДобавить короткий словарь и тест неизвестного значения
График растёт числом рядовВ label попал ID, URL или другой unbounded valueНайти label с почти уникальными значениямиПеренести контекст в лог или trace
Ошибки считают попыткиCounter увеличивается до определения исходаСопоставить точку инкремента с завершениемРазделить attempts и outcomes или перенести инкремент
Порог ломается после перезапускаСырые значения counter сравнивают как gaugeПроверить reset и range vectorИспользовать rate() или increase()
Нет записи до первой ошибкиРяд появляется только после событияЗапросить известный labelset заранееИнициализировать нулевую серию, если это оправдано

Порядок внедрения

  1. Запишите вопрос: операция, исход, окно и действие после границы.
  2. Выберите границу завершения и определите успех и ошибку.
  3. Составьте allowlist labels. Оставьте измерения, которые можно перечислить и агрегировать.
  4. Назовите метрики по одной величине и одной базовой единице. Проверьте _total и _seconds.
  5. Добавьте проверки неизвестной операции, исхода и отрицательной длительности. Убедитесь, что labels не содержат пользовательских значений.
  6. Прогоните контролируемую серию и сравните ожидаемое число ошибок с exposition или API библиотеки.
  7. Проверьте PromQL на тестовом Prometheus. Смоделируйте reset, отсутствие ряда и задержку scrape.
  8. Привяжите каждую ветку порога к действию: открыть лог, повторить сценарий, проверить релиз или ничего не делать при учебном нуле.

Ограничения

Эта схема не показывает причину ошибки. Для неё понадобятся логи, трассы, код ответа, версия релиза и контекст внешних зависимостей. Она не измеряет пользовательский опыт, если запрос не дошёл до сервера или ответ испортился в браузере. Она не выбирает за команду SLO и не говорит, сколько ложных срабатываний допустимо.

Порог нельзя назначать по удобному числу. Нужны период наблюдения, стоимость ошибки, ожидаемый трафик и владелец реакции. Если трафик почти нулевой, две ошибки и две тысячи ошибок имеют одинаковое значение в абсолютном counter, но разный смысл для продукта. Для сравнения сервисов нужна доля ошибок или другой согласованный показатель, а не копирование окна.

Учебные операции, события, длительности и порог выдуманы. Здесь нет production-нагрузки, измеренного уменьшения инцидентов или готового alert. Проверяемый результат скромнее: один вопрос превращён в контракт, а контракт можно прогнать, запросить и связать с конкретным следующим действием.

Критерий готовности

Сигнал готов к первой проверке, если другой инженер без устного объяснения может назвать границу события, перечислить допустимые labels, воспроизвести учебные значения, получить ожидаемый результат запроса и пройти отрицательный путь с неизвестным label. После reset и пропущенного ряда команда понимает, что означает «нет данных», а что — «ошибок не было». Если пункт не выполняется, уточняйте контракт и проверку, а не добавляйте новые labels.

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

"} +{"index":267,"slug":"editorial-2020-08-practice-metrics-basics","title":"Метрики приложения: как превратить сбой в проверяемый сигнал","excerpt":"Общий график запросов не отвечает, где возникла ошибка и что проверять дальше. Разбираем небольшой контракт для HTTP-операции: границу события, тип метрики, ограниченные labels, учебный запрос и действие после порога.","contentHtml":"

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

Быстрый способ ухудшить сигнал — добавить в label полный URL, идентификатор пользователя или текст исключения. Prometheus получит множество почти уникальных временных рядов, но следующий шаг расследования не станет яснее. В этой заметке соберём небольшой учебный контракт: одна серверная HTTP-граница, стабильное имя операции, два исхода и отдельный способ искать контекст в логах или трассировке.

Сценарий и вопрос

Начните не с названия метрики, а с вопроса, на который должен ответить запрос. Например: «Были ли ошибки завершения checkout за последние десять минут и какую проверку выполнить, если их не меньше двух?» Здесь явно заданы операция, исход, окно и действие. Такой вопрос ограничивает модель: мы измеряем завершённую серверную операцию, а не весь пользовательский путь.

Граница события проходит там, где обработчик уже знает результат. Инкремент в начале обработчика считает попытки, но не доказывает успех. Для ошибок нужен тот же момент завершения и тот же словарь операций. Если внешний платёжный провайдер отвечает позже, это уже другая граница, которую следует измерять отдельным сигналом.

Контракт сигнала для учебного сценария
ВеличинаТип и единицаLabelsВопросНе доказывает
store_http_requests_totalcounter, завершённые запросыoperation, outcomeКакая операция и с каким исходом завершилась?Причину ошибки и путь до пользователя
store_http_request_duration_secondshistogram, секундыoperationКак распределяется длительность?Причину медленного ответа
increase(...) за 10 минутзапрос к counteroperation=checkout, outcome=errorПересекла ли фикстура учебный порог?Production-порог и SLO

Тип метрики следует из состояния

store_http_requests_total — накопительный counter. Он увеличивается при событии и может сброситься к нулю после перезапуска процесса. Сырое значение редко отвечает на вопрос о недавнем окне; для количества событий за интервал применяют функцию над изменением counter. В учебном запросе ниже используется increase(), а для скорости событий обычно применяют rate().

Gauge описывает состояние, которое может расти и уменьшаться: число запросов в работе, свободную память или температуру. Нельзя использовать gauge для числа ошибок за всё время работы процесса: новое значение затрёт предыдущий снимок. Тип выбирают по форме величины, а не по удобству вызова библиотеки.

Длительность лучше хранить в секундах и собирать как распределение. Классическая histogram представляет наблюдения bucket-счётчиками, а также суммой и количеством; по bucket-данным можно вычислять квантили и агрегировать экземпляры при согласованных границах. Summary тоже публикует сумму и количество, но его заранее рассчитанные квантили нельзя пересчитать для другого окна и нельзя корректно агрегировать между репликами. Поддержку native histograms и точный API конкретной библиотеки нужно проверять отдельно: учебный контракт ниже её не предполагает.

Имя метрики должно показывать величину и базовую единицу: суффикс _total у накопительного счётчика и _seconds у длительности. Не смешивайте миллисекунды и секунды под одним именем. Иначе запросы останутся синтаксически правильными, но сравнение будет ложным.

Выбор типа по форме данных
ТипЧто измеряетПримерПроверка ошибки выбора
CounterНакопленные события, с возможным resetЗавершённые HTTP-запросыЗначение не должно уменьшаться в обычной работе
GaugeТекущий снимок, который меняется в обе стороныЗапросы в работеНе применять rate() к gauge
Classic histogramРаспределение в заданных bucket, сумма и количествоДлительность запросаПроверить границы bucket и возможность агрегации
SummaryСумму, количество и заданные квантили окнаЛокальная оценка latencyНе складывать квантили разных экземпляров

Labels должны отвечать на вопрос

Каждая уникальная комбинация имени метрики и значений labels образует отдельный временной ряд. Поэтому operation должен брать значения из короткого словаря: например, catalog, checkout, profile. Для outcome достаточно success и error. Такой набор можно перечислить заранее, проверить тестом и использовать в запросах.

Не добавляйте в этот counter user_id, email, полный URL, request ID, текст исключения или произвольный статус из запроса. Это контекст отдельного события, а не агрегированное измерение. Поместите его в журнал или трассу и свяжите с метрикой через correlation ID. Label должен разделять небольшое число веток, а не хранить историю каждого пользователя.

Сначала зафиксируйте словарь значений label и вопрос, который он должен отвечать. Затем проверьте, сколько рядов создаст каждая комбинация и какой запрос использует это измерение. После этого можно публиковать сигнал. Если маршрут содержит ID заказа, нормализуйте его до шаблона вроде /orders/:id. Если и шаблонов слишком много, оставьте в метрике только операцию, а конкретный адрес ищите по логу.

Воспроизводимый учебный пример

Следующий фрагмент не подключает клиент Prometheus и не выдаёт результат production-нагрузки. Это маленькая проверка контракта, которую можно запустить в Node.js: она принимает четыре события, отбрасывает неизвестные значения и считает ошибки только после проверки их границы.

const allowedOperations = new Set(['catalog', 'checkout', 'profile']);\nconst allowedOutcomes = new Set(['success', 'error']);\nconst events = [\n  { operation: 'checkout', outcome: 'success', durationSeconds: 0.42 },\n  { operation: 'checkout', outcome: 'error', durationSeconds: 0.90 },\n  { operation: 'catalog', outcome: 'success', durationSeconds: 0.18 },\n  { operation: 'checkout', outcome: 'error', durationSeconds: 1.10 },\n];\n\nfor (const event of events) {\n  if (!allowedOperations.has(event.operation)) throw new Error('unknown operation');\n  if (!allowedOutcomes.has(event.outcome)) throw new Error('unknown outcome');\n  if (event.durationSeconds < 0) throw new Error('invalid duration');\n}\n\nconst checkoutErrors = events.filter((event) =>\n  event.operation === 'checkout' && event.outcome === 'error',\n).length;\nconsole.log(checkoutErrors); // 2

Ожидаемый вывод — 2. Это не значение Prometheus и не готовое правило оповещения. В настоящем обработчике после такой проверки библиотека увеличит counter с теми же labels и передаст длительность в histogram. Точный вызов зависит от языка и клиента, поэтому его нельзя выдавать за универсальный API.

Отрицательный путь здесь обязателен. Если новый endpoint молча создаёт неизвестное значение operation, график может продолжить работать, а стоимость хранения и смысл агрегации изменятся. В рабочем коде ошибку нужно вернуть вызывающему слою или записать в отдельный технический сигнал, а не продолжать публикацию с неподтверждённым label.

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

Запрос и границы порога

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

sum(increase(store_http_requests_total{operation=\"checkout\",outcome=\"error\"}[10m])) >= 2

Выражение суммирует изменение counter за десять минут и проверяет условие. Число 2 выбрано для упражнения: одна ошибка показывает, что label работает, две переводят пример в следующую ветку. Оно не получено из трафика, не является допустимой долей ошибок и не должно копироваться в production alert.

increase() работает по диапазону наблюдений и учитывает reset counter, но результат зависит от точек scrape и наличия ряда. Локальная разность двух чисел в памяти не доказывает, что PromQL вернёт такое же значение. После перезапуска процесса, задержки scrape или пропуска ряда нужна отдельная проверка. Отсутствие ряда также не следует автоматически читать как ноль ошибок.

Если сервис запущен в нескольких экземплярах, sum() объединяет их только при совместимом наборе labels. До запроса проверьте, не добавляет ли инфраструктура собственные измерения, и решите, нужно ли сохранить разрез по экземпляру для диагностики. Для пользовательского опыта метрику надо связать с логом, трассировкой и клиентской проверкой: серверный counter не доказывает, что браузер отобразил правильный экран.

Симптом → причина → проверка → действие

Диагностика первого сигнала
СимптомПричинаПроверкаДействие
Есть общий рост, но не видна операцияНет ограниченного label operationПосмотреть series и словарь операцийДобавить короткий словарь и тест неизвестного значения
График быстро растёт числом рядовВ label попал ID, URL или другой unbounded valueНайти значения с высокой уникальностьюПеренести контекст в лог или trace
Ошибки считают попыткиCounter увеличивается до определения исходаСопоставить точку инкремента с завершениемРазделить attempts и outcomes или перенести инкремент
Порог ведёт себя неверно после перезапускаСырые значения counter сравнивают как gaugeПроверить reset и range vectorИспользовать rate() или increase()
Нет записи до первой ошибкиРяд появляется только после событияЗапросить известный labelset заранееИнициализировать нулевую серию, если это оправдано

Порядок внедрения

  1. Запишите операционный вопрос: операция, исход, окно и действие после порога.
  2. Выберите границу завершения и определите, что считается успехом и ошибкой.
  3. Составьте allowlist для labels и оставьте только измерения, которые можно перечислить и агрегировать.
  4. Назовите метрики по одной величине и базовой единице; проверьте _total и _seconds.
  5. Добавьте проверки неизвестной операции, исхода и отрицательной длительности.
  6. Прогоните контролируемую серию и сравните ожидаемое число ошибок с exposition или API выбранной библиотеки.
  7. Проверьте PromQL на тестовом Prometheus; смоделируйте reset, отсутствие ряда и задержку scrape.
  8. Свяжите каждую ветку порога с действием: открыть лог, повторить сценарий, проверить релиз или ничего не делать при учебном нуле.

Ограничения

Эта схема показывает контракт сигнала, но не причину ошибки. Для причины понадобятся логи, трассы, код ответа, версия релиза и контекст внешних зависимостей. Она не измеряет путь браузера, DNS, сеть и базу, если запрос не дошёл до сервера. Она также не выбирает за команду SLO и не говорит, сколько ложных срабатываний допустимо.

Точный синтаксис клиента, поддержка native histograms и формат exposition зависят от версии и языка. Для старой установки сначала сверяйте версию Prometheus и документацию используемой библиотеки. Нельзя переносить учебный порог 2, словарь операций или bucket-границы в production без наблюдения и отдельного решения владельца сервиса.

Учебные операции, события, длительности и порог выдуманы. Здесь нет production-нагрузки, измеренного уменьшения инцидентов или готового alert. Проверяемый результат скромнее: один вопрос превращён в контракт, который можно прогнать, запросить и связать с конкретным следующим действием.

Критерий готовности

Сигнал готов к первой проверке, если другой инженер без устного объяснения может назвать границу события, перечислить допустимые labels, воспроизвести учебный вывод 2, выполнить запрос и пройти отрицательный путь с неизвестным label. После reset и пропущенного ряда команда понимает, что означает «нет данных», а что — «ошибок не было». Если пункт не выполняется, уточняйте контракт и проверку, а не добавляйте новые labels.

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

"}