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 без причины или потратить час на чтение логов, которые не связаны с нужной операцией.
Тезис простой: метрика помогает принять решение только тогда, когда её границы совпадают с вопросом. Нужно назвать операцию, событие, окно и следующий шаг. Один общий счётчик показывает, что система что-то делала. Он не показывает, сколько ошибок произошло в checkout и можно ли связать их с конкретным запросом.
\nДля завершённых запросов подходит counter. Он накапливает события и обычно растёт. Для вопроса «сколько ошибок checkout появилось за десять минут» нужен прирост counter, а не его последняя сырая величина. Для вопроса «сколько запросов выполняется сейчас» нужен gauge. Для вопроса «как распределилась длительность» нужна отдельная метрика длительности, например histogram.
\nПоследнее значение store_http_requests_total зависит от времени жизни процесса. Оно не равно числу ошибок за выбранное окно. В PromQL такой вопрос выражает increase():
# Учебный запрос. Не production-alert без проверки окружения.\nsum(increase(store_http_requests_total{operation="checkout", outcome="error"}[10m])) >= 2\nВыражение выбирает series с двумя bounded labels, считает прирост за десять минут и сравнивает его с учебной границей. Оно не сообщает причину ошибки. После срабатывания нужно открыть связанный журнал, trace или изолированный сценарий. Если метрика не покрывает пользовательский путь, scrape пропущен или label выбран неверно, нулевой результат не доказывает, что пользовательская ошибка исчезла.
\noperation=checkout отделяет логическую операцию. outcome=error отделяет ошибку от успешного завершения. Набор ограничен заранее: операции и исходы берутся из небольшого списка. Такой label позволяет агрегировать данные и сравнивать ветки.
Не добавляйте в labels request_id, email, полный URL или другой идентификатор, который почти уникален для каждого события. Каждая комбинация labels создаёт отдельную time series. Уникальный идентификатор превратит counter в поток почти одноразовых рядов. График потеряет агрегирование, а Prometheus получит лишнюю память, CPU, диск и сетевой трафик. Конкретный запрос ищут в журнале по корреляционному идентификатору.
# Хорошая граница метрики\nstore_http_requests_total{operation="checkout", outcome="error"}\n\n# Плохая граница: уникальный label разрушает агрегацию\nstore_http_requests_total{operation="checkout", request_id="8f2d..."}\nЕсли нужна доля ошибок, знаменатель должен описывать ту же границу. Ошибки, посчитанные внутри приложения, нельзя без проверки делить на все попытки, посчитанные на proxy. Сначала убедитесь, что обе величины относятся к одной операции, одному времени и одной точке завершения.
\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.
| Симптом | Возможная причина | Проверка | Действие |
|---|---|---|---|
Пользователь сообщает об ошибке, а общий 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 | Открыть ограниченную ветку расследования |
Таблица разделяет наблюдение и действие. Значение два в учебном примере — не универсальный порог. В production порог зависит от объёма трафика, допустимой доли ошибок, стоимости ложного сигнала, времени реакции и владельца. Если эти условия не названы, число выглядит точным, но не является рабочим правилом.
\nМетрика не видит событие, которое не дошло до точки instrumentation. Ошибка в браузере может произойти до приложения. Пропущенный scrape оставит окно неполным. Перезапуск процесса изменит сырое значение counter, хотя корректная функция запроса умеет учитывать reset. Неправильный label может спрятать нужную операцию в общей series.
\nПоэтому ноль ошибок не равен нулю пользовательских проблем. Проверьте, что target жив, series существует, timestamp попадает в окно, а журнал содержит тот же путь. Если условия не выполняются, честный результат — «сигнал недостаточен», а не «система исправна». Это отрицательное решение экономит время и не создаёт ложной уверенности.
\nУчебные snapshots и код выше не измеряют память Prometheus, latency, нагрузку, доставку alert или поведение exporter. Они не подтверждают production-результаты. Их задача — показать связь между операционным вопросом, metric type, labels, окном и проверкой. Для реального внедрения нужен отдельный стенд с известным scrape, контролируемым запросом и сохранённым ответом PromQL.
\nРазбор готов, если другой инженер может повторить его без устной подсказки: назвать series и её labels, объяснить момент увеличения counter, выполнить запрос с указанным окном, увидеть результат на доступном наборе данных и перейти от срабатывания к одному связанному журналу или trace. Дополнительно он должен уметь показать отрицательный путь: почему ноль не закрывает жалобу, если metric не покрывает endpoint или scrape пропущен.
\nincrease(), counter reset и расчёта прироста в range vector.Рассмотрим дежурный сценарий: пользователь сообщает, что checkout иногда завершается ошибкой. На dashboard линия requests_total растёт ровно, аварий в логах нет. Команда смотрит на общий график и не понимает, что проверять дальше. Цена ошибки — не только пропущенный сбой. Можно поднять шумный alert, увеличить timeout без причины или потратить час на чтение логов, которые не связаны с нужной операцией.
Вопрос здесь не в количестве линий на графике. Метрика помогает принять решение, когда её границы совпадают с вопросом: названы операция, событие, окно и следующий шаг. Один общий счётчик показывает, что система что-то делала. Он не показывает, сколько ошибок произошло в checkout и можно ли связать их с конкретным запросом.
\nДо выбора типа метрики запишите наблюдаемый симптом без диагноза: какой пользовательский путь нарушен, в какое время это происходит и какое действие нельзя делать вслепую. В нашем примере нужно ответить на узкий вопрос: сколько завершённых запросов checkout закончилось ошибкой за последние десять минут?
\nТакой вопрос сразу задаёт границы. Событие — завершённый запрос с известным исходом. Операция — checkout. Окно — десять минут. Следующий шаг после сигнала — открыть журнал или trace, а не объявить причину найденной. Если поменять любую из этих границ, число перестанет отвечать на исходный вопрос.
\nДля завершённых запросов подходит counter — накопительная метрика событий. Она растёт и может сброситься при перезапуске процесса. Для вопроса «сколько ошибок checkout появилось за десять минут» нужен прирост counter, а не его последняя сырая величина.
\nДля вопроса «сколько запросов выполняется сейчас» нужен gauge: он описывает текущее состояние и может расти или уменьшаться. Для вопроса «как распределилась длительность» нужна отдельная метрика наблюдений, например histogram. Не называйте один тип другим только потому, что все они отображаются линиями.
\nПоследнее значение store_http_requests_total зависит от времени жизни процесса. Оно не равно числу ошибок за выбранное окно. В PromQL такой вопрос выражает increase():
# Учебный запрос. Не production-alert без проверки окружения.\nsum(increase(store_http_requests_total{operation="checkout", outcome="error"}[10m])) >= 2\nВыражение выбирает series с двумя ограниченными labels, считает прирост за десять минут и сравнивает его с учебной границей. Оно не сообщает причину ошибки. После срабатывания нужно открыть связанный журнал, trace или изолированный сценарий.
\nincrease() учитывает reset counter после перезапуска target и рассчитывает прирост на указанном диапазоне. Поэтому ручная разность двух крайних snapshots полезна для объяснения идеи, но не должна подменять выполнение PromQL. На результат влияют время samples, scrape interval, пропуски scrape и границы range vector.
operation=checkout отделяет логическую операцию. outcome=error отделяет ошибку от успешного завершения. Набор значений ограничен заранее: операции и исходы берутся из небольшого списка. Такой label позволяет агрегировать данные и сравнивать ветки, не создавая отдельное имя метрики для каждой ветки.
Не добавляйте в labels request_id, email, полный URL или другой идентификатор, который почти уникален для каждого события. Каждая комбинация labels создаёт отдельную time series. Уникальный идентификатор превратит counter в поток почти одноразовых рядов. График потеряет агрегирование, а Prometheus получит дополнительные затраты памяти, CPU, диска и сети. Конкретный запрос ищут в журнале по корреляционному идентификатору.
# Хорошая граница метрики\nstore_http_requests_total{operation="checkout", outcome="error"}\n\n# Плохая граница: уникальный label разрушает агрегацию\nstore_http_requests_total{operation="checkout", request_id="8f2d..."}\nЕсли нужна доля ошибок, знаменатель должен описывать ту же границу. Ошибки, посчитанные внутри приложения, нельзя без проверки делить на все попытки, посчитанные на proxy. Сначала убедитесь, что обе величины относятся к одной операции, одному времени и одной точке завершения.
\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.
В настоящем Prometheus increase() работает по samples range vector, а не по обещанию ровно трёх точек. Она корректирует reset counter и экстраполирует расчёт на границы окна. Поэтому простая разность полезна как контрольный пример, но не как формула для ручного production-расчёта.
| Симптом | Возможная причина | Проверка | Действие |
|---|---|---|---|
Пользователь сообщает об ошибке, а общий 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 | Открыть ограниченную ветку расследования |
Таблица разделяет наблюдение и действие. Значение два в учебном примере — не универсальный порог. В production порог зависит от объёма трафика, допустимой доли ошибок, стоимости ложного сигнала, времени реакции и владельца. Если эти условия не названы, число выглядит точным, но не является рабочим правилом.
\nМетрика не видит событие, которое не дошло до точки instrumentation. Ошибка в браузере может произойти до приложения. Пропущенный scrape оставит окно неполным. Перезапуск процесса изменит сырое значение counter, хотя функция запроса умеет учитывать reset. Неправильный label может спрятать нужную операцию в общей series.
\nПоэтому ноль ошибок не равен нулю пользовательских проблем. Проверьте, что target жив, series существует, timestamp попадает в окно, а журнал содержит тот же путь. Если условия не выполняются, честный результат — «сигнал недостаточен», а не «система исправна». Это отрицательное решение экономит время и не создаёт ложной уверенности.
\nУчебные snapshots и код выше не измеряют память Prometheus, latency, нагрузку, доставку alert или поведение exporter. Они не подтверждают production-результаты. Их задача — показать связь между операционным вопросом, типом метрики, labels, окном и проверкой. Для реального внедрения нужен отдельный стенд с известным scrape, контролируемым запросом и сохранённым ответом PromQL.
\nРазбор готов, если другой инженер может повторить его без устной подсказки: назвать series и её labels, объяснить момент увеличения counter, выполнить запрос с указанным окном, увидеть результат на доступном наборе данных и перейти от срабатывания к одному связанному журналу или trace. Дополнительно он должен показать отрицательный путь: почему ноль не закрывает жалобу, если metric не покрывает endpoint или scrape пропущен.
\nincrease(), counter reset и расчёта прироста в range vector.