From 710aceec6d366627f4a9ad821148619488626666 Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 21:58:34 +0300 Subject: [PATCH] editorial: revise article 265 metrics --- editorial/agent-rewrites/265.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editorial/agent-rewrites/265.json b/editorial/agent-rewrites/265.json index c5fb677..f62e6b9 100644 --- a/editorial/agent-rewrites/265.json +++ b/editorial/agent-rewrites/265.json @@ -3,5 +3,5 @@ "slug": "editorial-2020-08-field-metrics-basics", "title": "Метрики приложения: как превратить график в проверяемое действие", "excerpt": "Пользователь видит ошибку, а общий график запросов не объясняет причину. Разбираем counter, окно, labels и проверку учебного порога без выдуманных production-выводов.", - "contentHtml": "

Пользователь сообщает: checkout иногда завершается ошибкой. На dashboard линия requests_total растёт ровно, аварий в логах нет. Команда смотрит на общий график и не понимает, что проверять дальше. Цена ошибки — не только пропущенный сбой. Можно поднять шумный alert, увеличить timeout без причины или потратить час на чтение логов, которые не связаны с нужной операцией.

\n

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

\n

Сначала отделите событие от состояния

\n

Для завершённых запросов подходит counter. Он накапливает события и обычно растёт. Для вопроса «сколько ошибок checkout появилось за десять минут» нужен прирост counter, а не его последняя сырая величина. Для вопроса «сколько запросов выполняется сейчас» нужен gauge. Для вопроса «как распределилась длительность» нужна отдельная метрика длительности, например histogram.

\n

Последнее значение store_http_requests_total зависит от времени жизни процесса. Оно не равно числу ошибок за выбранное окно. В PromQL такой вопрос выражает increase():

\n
# Учебный запрос. Не production-alert без проверки окружения.\nsum(increase(store_http_requests_total{operation="checkout", outcome="error"}[10m])) >= 2
\n

Выражение выбирает series с двумя bounded labels, считает прирост за десять минут и сравнивает его с учебной границей. Оно не сообщает причину ошибки. После срабатывания нужно открыть связанный журнал, trace или изолированный сценарий. Если метрика не покрывает пользовательский путь, scrape пропущен или label выбран неверно, нулевой результат не доказывает, что пользовательская ошибка исчезла.

\n

Labels должны помогать сузить вопрос

\n

operation=checkout отделяет логическую операцию. outcome=error отделяет ошибку от успешного завершения. Набор ограничен заранее: операции и исходы берутся из небольшого списка. Такой label позволяет агрегировать данные и сравнивать ветки.

\n

Не добавляйте в labels request_id, email, полный URL или другой идентификатор, который почти уникален для каждого события. Каждая комбинация labels создаёт отдельную time series. Уникальный идентификатор превратит counter в поток почти одноразовых рядов. График потеряет агрегирование, а Prometheus получит лишнюю память, CPU, диск и сетевой трафик. Конкретный запрос ищут в журнале по корреляционному идентификатору.

\n
# Хорошая граница метрики\nstore_http_requests_total{operation="checkout", outcome="error"}\n\n# Плохая граница: уникальный label разрушает агрегацию\nstore_http_requests_total{operation="checkout", request_id="8f2d..."}
\n

Если нужна доля ошибок, знаменатель должен описывать ту же границу. Ошибки, посчитанные внутри приложения, нельзя без проверки делить на все попытки, посчитанные на proxy. Сначала убедитесь, что обе величины относятся к одной операции, одному времени и одной точке завершения.

\n

Учебная серия и её пределы

\n

Ниже приведены выдуманные snapshots. Они нужны только для объяснения расчёта. В этом примере counter ошибки checkout равен нулю в 10:00, единице в 10:05 и двум в 10:10:

\n
# Не production telemetry. Значения заданы для учебного примера.\nstore_http_requests_total{operation="checkout",outcome="error"}\n2020-08-01T10:00:00Z  0\n2020-08-01T10:05:00Z  1\n2020-08-01T10:10:00Z  2\n\n# В упрощённой модели: 2 - 0 = 2\n# Учебная граница >= 2 пересечена.
\n

В учебной модели прирост равен двум. В настоящем Prometheus increase() учитывает диапазон samples и корректирует counter reset после перезапуска target. Поэтому простая разность крайних точек полезна для объяснения, но не заменяет выполнение PromQL на сервере. На результат также влияют scrape interval, пропуски scrape и границы range vector.

\n
Схема диагностики метрики: от общего графика к counter ошибок checkout, проверке окна и связанному журналу
Порог переводит общий симптом в ограниченную проверку. Он не называет причину и не заменяет журнал.
\n

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

\n
Как не сделать из одной линии графика ложный диагноз
СимптомВозможная причинаПроверкаДействие
Пользователь сообщает об ошибке, а общий requests_total растётСчётчик смешивает операции и исходыПроверить metric name и доступные labelsВыбрать bounded operation и outcome; не менять timeout
increase(...[10m]) = 0В series не было прироста или путь не инструментированСверить operation с жалобой, время scrape и targetНе объявлять систему здоровой; проверить журнал и покрытие
Последняя величина counter большаяПроцесс давно работаетСравнить прирост за окно, а не абсолютное значениеИспользовать increase()
У каждой ошибки отдельная seriesВ label попал request ID или полный URLНайти динамические значения и посчитать seriesУбрать уникальное поле из metric contract; оставить его в логе
Порог срабатывает после редкого scrapeОкно и частота сбора не согласованыСопоставить range vector, scrape interval и пропускиИзменить окно или сбор после проверки владельца alert
Две ошибки есть, но причина неизвестнаМетрика агрегирует событие без контекстаНайти trace или журнал по времени и correlation IDОткрыть ограниченную ветку расследования
\n

Таблица разделяет наблюдение и действие. Значение два в учебном примере — не универсальный порог. В production порог зависит от объёма трафика, допустимой доли ошибок, стоимости ложного сигнала, времени реакции и владельца. Если эти условия не названы, число выглядит точным, но не является рабочим правилом.

\n

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

\n
  1. Запишите симптом без диагноза: какой пользовательский путь нарушен, когда это происходит и чем опасна ошибка.
  2. Назовите событие, которое увеличивает counter. Для checkout это завершённый запрос с известным исходом, а не начало попытки.
  3. Выберите тип метрики. Counter отвечает за накопленные события, gauge — за текущее состояние, histogram — за распределение наблюдений.
  4. Проверьте labels. Оставьте ограниченные значения операции и исхода. Уникальные идентификаторы отправьте в журнал или trace context.
  5. Согласуйте окно с частотой scrape. Укажите, что считается приростом и на какой границе он измеряется.
  6. Проверьте учебные snapshots арифметикой. Отдельно выполните PromQL на сервере метрик и проверьте результат на реальном наборе series.
  7. Если порог пересечён, найдите один связанный журнал или trace. Сравните время, operation, outcome и correlation ID.
  8. Только после этого меняйте код, timeout, retry или правило alert. Зафиксируйте критерий отката.
\n

Ограничения и отрицательный путь

\n

Метрика не видит событие, которое не дошло до точки instrumentation. Ошибка в браузере может произойти до приложения. Пропущенный scrape оставит окно неполным. Перезапуск процесса изменит сырое значение counter, хотя корректная функция запроса умеет учитывать reset. Неправильный label может спрятать нужную операцию в общей series.

\n

Поэтому ноль ошибок не равен нулю пользовательских проблем. Проверьте, что target жив, series существует, timestamp попадает в окно, а журнал содержит тот же путь. Если условия не выполняются, честный результат — «сигнал недостаточен», а не «система исправна». Это отрицательное решение экономит время и не создаёт ложной уверенности.

\n

Учебные snapshots и код выше не измеряют память Prometheus, latency, нагрузку, доставку alert или поведение exporter. Они не подтверждают production-результаты. Их задача — показать связь между операционным вопросом, metric type, labels, окном и проверкой. Для реального внедрения нужен отдельный стенд с известным scrape, контролируемым запросом и сохранённым ответом PromQL.

\n

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

\n

Разбор готов, если другой инженер может повторить его без устной подсказки: назвать series и её labels, объяснить момент увеличения counter, выполнить запрос с указанным окном, увидеть результат на доступном наборе данных и перейти от срабатывания к одному связанному журналу или trace. Дополнительно он должен уметь показать отрицательный путь: почему ноль не закрывает жалобу, если metric не покрывает endpoint или scrape пропущен.

\n

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

\n" + "contentHtml": "

Рассмотрим дежурный сценарий: пользователь сообщает, что checkout иногда завершается ошибкой. На dashboard линия requests_total растёт ровно, аварий в логах нет. Команда смотрит на общий график и не понимает, что проверять дальше. Цена ошибки — не только пропущенный сбой. Можно поднять шумный alert, увеличить timeout без причины или потратить час на чтение логов, которые не связаны с нужной операцией.

\n

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

\n

Сначала зафиксируйте вопрос

\n

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

\n

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

\n

Отделите событие от состояния

\n

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

\n

Для вопроса «сколько запросов выполняется сейчас» нужен gauge: он описывает текущее состояние и может расти или уменьшаться. Для вопроса «как распределилась длительность» нужна отдельная метрика наблюдений, например histogram. Не называйте один тип другим только потому, что все они отображаются линиями.

\n

Последнее значение store_http_requests_total зависит от времени жизни процесса. Оно не равно числу ошибок за выбранное окно. В PromQL такой вопрос выражает increase():

\n
# Учебный запрос. Не production-alert без проверки окружения.\nsum(increase(store_http_requests_total{operation="checkout", outcome="error"}[10m])) >= 2
\n

Выражение выбирает series с двумя ограниченными labels, считает прирост за десять минут и сравнивает его с учебной границей. Оно не сообщает причину ошибки. После срабатывания нужно открыть связанный журнал, trace или изолированный сценарий.

\n

increase() учитывает reset counter после перезапуска target и рассчитывает прирост на указанном диапазоне. Поэтому ручная разность двух крайних snapshots полезна для объяснения идеи, но не должна подменять выполнение PromQL. На результат влияют время samples, scrape interval, пропуски scrape и границы range vector.

\n

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

\n

operation=checkout отделяет логическую операцию. outcome=error отделяет ошибку от успешного завершения. Набор значений ограничен заранее: операции и исходы берутся из небольшого списка. Такой label позволяет агрегировать данные и сравнивать ветки, не создавая отдельное имя метрики для каждой ветки.

\n

Не добавляйте в labels request_id, email, полный URL или другой идентификатор, который почти уникален для каждого события. Каждая комбинация labels создаёт отдельную time series. Уникальный идентификатор превратит counter в поток почти одноразовых рядов. График потеряет агрегирование, а Prometheus получит дополнительные затраты памяти, CPU, диска и сети. Конкретный запрос ищут в журнале по корреляционному идентификатору.

\n
# Хорошая граница метрики\nstore_http_requests_total{operation="checkout", outcome="error"}\n\n# Плохая граница: уникальный label разрушает агрегацию\nstore_http_requests_total{operation="checkout", request_id="8f2d..."}
\n

Если нужна доля ошибок, знаменатель должен описывать ту же границу. Ошибки, посчитанные внутри приложения, нельзя без проверки делить на все попытки, посчитанные на proxy. Сначала убедитесь, что обе величины относятся к одной операции, одному времени и одной точке завершения.

\n

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

\n

Ниже приведены выдуманные snapshots. Они нужны только для проверки арифметики. В этом примере counter ошибки checkout равен нулю в 10:00, единице в 10:05 и двум в 10:10:

\n
# Не production telemetry. Значения заданы для учебного примера.\nstore_http_requests_total{operation="checkout",outcome="error"}\n2020-08-01T10:00:00Z  0\n2020-08-01T10:05:00Z  1\n2020-08-01T10:10:00Z  2\n\n# В упрощённой модели: 2 - 0 = 2\n# Учебная граница >= 2 пересечена.
\n

Повторить разбор можно в таком порядке: зафиксировать ту же series, подставить три значения в выбранное окно, проверить арифметику 2 - 0 = 2, затем выполнить PromQL на доступном сервере метрик. Отдельно повторите пример с последним значением 1: он не должен пересекать границу >= 2. Это проверяет учебную логику, но не проверяет exporter, scrape или alert delivery.

\n

В настоящем Prometheus increase() работает по samples range vector, а не по обещанию ровно трёх точек. Она корректирует reset counter и экстраполирует расчёт на границы окна. Поэтому простая разность полезна как контрольный пример, но не как формула для ручного production-расчёта.

\n
Схема диагностики метрики: от общего графика к counter ошибок checkout, проверке окна и связанному журналу
Порог переводит общий симптом в ограниченную проверку. Он не называет причину и не заменяет журнал.
\n

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

\n
Как не сделать из одной линии графика ложный диагноз
СимптомВозможная причинаПроверкаДействие
Пользователь сообщает об ошибке, а общий requests_total растётСчётчик смешивает операции и исходыПроверить metric name и доступные labelsВыбрать bounded operation и outcome; не менять timeout
increase(...[10m]) = 0В series не было прироста или путь не инструментированСверить operation с жалобой, время scrape и targetНе объявлять систему здоровой; проверить журнал и покрытие
Последняя величина counter большаяПроцесс давно работаетСравнить прирост за окно, а не абсолютное значениеИспользовать increase()
У каждой ошибки отдельная seriesВ label попал request ID или полный URLНайти динамические значения и посчитать seriesУбрать уникальное поле из metric contract; оставить его в логе
Порог срабатывает после редкого scrapeОкно и частота сбора не согласованыСопоставить range vector, scrape interval и пропускиИзменить окно или сбор после проверки владельца alert
Две ошибки есть, но причина неизвестнаМетрика агрегирует событие без контекстаНайти trace или журнал по времени и correlation IDОткрыть ограниченную ветку расследования
\n

Таблица разделяет наблюдение и действие. Значение два в учебном примере — не универсальный порог. В production порог зависит от объёма трафика, допустимой доли ошибок, стоимости ложного сигнала, времени реакции и владельца. Если эти условия не названы, число выглядит точным, но не является рабочим правилом.

\n

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

\n
  1. Запишите симптом без диагноза: какой пользовательский путь нарушен, когда это происходит и чем опасна ошибка.
  2. Назовите событие, которое увеличивает counter. Для checkout это завершённый запрос с известным исходом, а не начало попытки.
  3. Выберите тип метрики. Counter отвечает за накопленные события, gauge — за текущее состояние, histogram — за распределение наблюдений.
  4. Проверьте labels. Оставьте ограниченные значения операции и исхода. Уникальные идентификаторы отправьте в журнал или trace context.
  5. Согласуйте окно с частотой scrape. Укажите, что считается приростом и на какой границе он измеряется.
  6. Проверьте учебные snapshots арифметикой. Отдельно выполните PromQL на сервере метрик и проверьте результат на реальном наборе series.
  7. Если порог пересечён, найдите один связанный журнал или trace. Сравните время, operation, outcome и correlation ID.
  8. Только после этого меняйте код, timeout, retry или правило alert. Зафиксируйте критерий отката.
\n

Ограничения и отрицательный путь

\n

Метрика не видит событие, которое не дошло до точки instrumentation. Ошибка в браузере может произойти до приложения. Пропущенный scrape оставит окно неполным. Перезапуск процесса изменит сырое значение counter, хотя функция запроса умеет учитывать reset. Неправильный label может спрятать нужную операцию в общей series.

\n

Поэтому ноль ошибок не равен нулю пользовательских проблем. Проверьте, что target жив, series существует, timestamp попадает в окно, а журнал содержит тот же путь. Если условия не выполняются, честный результат — «сигнал недостаточен», а не «система исправна». Это отрицательное решение экономит время и не создаёт ложной уверенности.

\n

Учебные snapshots и код выше не измеряют память Prometheus, latency, нагрузку, доставку alert или поведение exporter. Они не подтверждают production-результаты. Их задача — показать связь между операционным вопросом, типом метрики, labels, окном и проверкой. Для реального внедрения нужен отдельный стенд с известным scrape, контролируемым запросом и сохранённым ответом PromQL.

\n

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

\n

Разбор готов, если другой инженер может повторить его без устной подсказки: назвать series и её labels, объяснить момент увеличения counter, выполнить запрос с указанным окном, увидеть результат на доступном наборе данных и перейти от срабатывания к одному связанному журналу или trace. Дополнительно он должен показать отрицательный путь: почему ноль не закрывает жалобу, если metric не покрывает endpoint или scrape пропущен.

\n

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

\n" }