Files
progcode/editorial/agent-rewrites/265.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
16 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 265,
"slug": "editorial-2020-08-field-metrics-basics",
"title": "Метрики приложения: как превратить график в проверяемое действие",
"excerpt": "Пользователь видит ошибку, а общий график запросов не объясняет причину. Разбираем counter, окно, labels и проверку учебного порога без выдуманных production-выводов.",
"contentHtml": "<p>Пользователь сообщает: checkout иногда завершается ошибкой. На dashboard линия <code>requests_total</code> растёт ровно, аварий в логах нет. Команда смотрит на общий график и не понимает, что проверять дальше. Цена ошибки — не только пропущенный сбой. Можно поднять шумный alert, увеличить timeout без причины или потратить час на чтение логов, которые не связаны с нужной операцией.</p>\n<p>Тезис простой: метрика помогает принять решение только тогда, когда её границы совпадают с вопросом. Нужно назвать операцию, событие, окно и следующий шаг. Один общий счётчик показывает, что система что-то делала. Он не показывает, сколько ошибок произошло в checkout и можно ли связать их с конкретным запросом.</p>\n<h2>Сначала отделите событие от состояния</h2>\n<p>Для завершённых запросов подходит <em>counter</em>. Он накапливает события и обычно растёт. Для вопроса «сколько ошибок checkout появилось за десять минут» нужен прирост counter, а не его последняя сырая величина. Для вопроса «сколько запросов выполняется сейчас» нужен <em>gauge</em>. Для вопроса «как распределилась длительность» нужна отдельная метрика длительности, например histogram.</p>\n<p>Последнее значение <code>store_http_requests_total</code> зависит от времени жизни процесса. Оно не равно числу ошибок за выбранное окно. В PromQL такой вопрос выражает <code>increase()</code>:</p>\n<pre><code># Учебный запрос. Не production-alert без проверки окружения.\nsum(increase(store_http_requests_total{operation=&quot;checkout&quot;, outcome=&quot;error&quot;}[10m])) &gt;= 2</code></pre>\n<p>Выражение выбирает series с двумя bounded labels, считает прирост за десять минут и сравнивает его с учебной границей. Оно не сообщает причину ошибки. После срабатывания нужно открыть связанный журнал, trace или изолированный сценарий. Если метрика не покрывает пользовательский путь, scrape пропущен или label выбран неверно, нулевой результат не доказывает, что пользовательская ошибка исчезла.</p>\n<h2>Labels должны помогать сузить вопрос</h2>\n<p><code>operation=checkout</code> отделяет логическую операцию. <code>outcome=error</code> отделяет ошибку от успешного завершения. Набор ограничен заранее: операции и исходы берутся из небольшого списка. Такой label позволяет агрегировать данные и сравнивать ветки.</p>\n<p>Не добавляйте в labels <code>request_id</code>, email, полный URL или другой идентификатор, который почти уникален для каждого события. Каждая комбинация labels создаёт отдельную time series. Уникальный идентификатор превратит counter в поток почти одноразовых рядов. График потеряет агрегирование, а Prometheus получит лишнюю память, CPU, диск и сетевой трафик. Конкретный запрос ищут в журнале по корреляционному идентификатору.</p>\n<pre><code># Хорошая граница метрики\nstore_http_requests_total{operation=&quot;checkout&quot;, outcome=&quot;error&quot;}\n\n# Плохая граница: уникальный label разрушает агрегацию\nstore_http_requests_total{operation=&quot;checkout&quot;, request_id=&quot;8f2d...&quot;}</code></pre>\n<p>Если нужна доля ошибок, знаменатель должен описывать ту же границу. Ошибки, посчитанные внутри приложения, нельзя без проверки делить на все попытки, посчитанные на proxy. Сначала убедитесь, что обе величины относятся к одной операции, одному времени и одной точке завершения.</p>\n<h2>Учебная серия и её пределы</h2>\n<p>Ниже приведены выдуманные snapshots. Они нужны только для объяснения расчёта. В этом примере counter ошибки checkout равен нулю в 10:00, единице в 10:05 и двум в 10:10:</p>\n<pre><code># Не production telemetry. Значения заданы для учебного примера.\nstore_http_requests_total{operation=&quot;checkout&quot;,outcome=&quot;error&quot;}\n2020-08-01T10:00:00Z 0\n2020-08-01T10:05:00Z 1\n2020-08-01T10:10:00Z 2\n\n# В упрощённой модели: 2 - 0 = 2\n# Учебная граница &gt;= 2 пересечена.</code></pre>\n<p>В учебной модели прирост равен двум. В настоящем Prometheus <code>increase()</code> учитывает диапазон samples и корректирует counter reset после перезапуска target. Поэтому простая разность крайних точек полезна для объяснения, но не заменяет выполнение PromQL на сервере. На результат также влияют scrape interval, пропуски scrape и границы range vector.</p>\n<figure><img src='/assets/editorial/2020/metrics-diagnosis-2020.svg' alt='Схема диагностики метрики: от общего графика к counter ошибок checkout, проверке окна и связанному журналу' loading='lazy' /><figcaption>Порог переводит общий симптом в ограниченную проверку. Он не называет причину и не заменяет журнал.</figcaption></figure>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class='table-scroll'><table><caption>Как не сделать из одной линии графика ложный диагноз</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Возможная причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Пользователь сообщает об ошибке, а общий <code>requests_total</code> растёт</td><td>Счётчик смешивает операции и исходы</td><td>Проверить metric name и доступные labels</td><td>Выбрать bounded <code>operation</code> и <code>outcome</code>; не менять timeout</td></tr><tr><td><code>increase(...[10m]) = 0</code></td><td>В series не было прироста или путь не инструментирован</td><td>Сверить operation с жалобой, время scrape и target</td><td>Не объявлять систему здоровой; проверить журнал и покрытие</td></tr><tr><td>Последняя величина counter большая</td><td>Процесс давно работает</td><td>Сравнить прирост за окно, а не абсолютное значение</td><td>Использовать <code>increase()</code></td></tr><tr><td>У каждой ошибки отдельная series</td><td>В label попал request ID или полный URL</td><td>Найти динамические значения и посчитать series</td><td>Убрать уникальное поле из metric contract; оставить его в логе</td></tr><tr><td>Порог срабатывает после редкого scrape</td><td>Окно и частота сбора не согласованы</td><td>Сопоставить range vector, scrape interval и пропуски</td><td>Изменить окно или сбор после проверки владельца alert</td></tr><tr><td>Две ошибки есть, но причина неизвестна</td><td>Метрика агрегирует событие без контекста</td><td>Найти trace или журнал по времени и correlation ID</td><td>Открыть ограниченную ветку расследования</td></tr></tbody></table></div>\n<p>Таблица разделяет наблюдение и действие. Значение два в учебном примере — не универсальный порог. В production порог зависит от объёма трафика, допустимой доли ошибок, стоимости ложного сигнала, времени реакции и владельца. Если эти условия не названы, число выглядит точным, но не является рабочим правилом.</p>\n<h2>Порядок проверки</h2>\n<ol><li>Запишите симптом без диагноза: какой пользовательский путь нарушен, когда это происходит и чем опасна ошибка.</li><li>Назовите событие, которое увеличивает counter. Для checkout это завершённый запрос с известным исходом, а не начало попытки.</li><li>Выберите тип метрики. Counter отвечает за накопленные события, gauge — за текущее состояние, histogram — за распределение наблюдений.</li><li>Проверьте labels. Оставьте ограниченные значения операции и исхода. Уникальные идентификаторы отправьте в журнал или trace context.</li><li>Согласуйте окно с частотой scrape. Укажите, что считается приростом и на какой границе он измеряется.</li><li>Проверьте учебные snapshots арифметикой. Отдельно выполните PromQL на сервере метрик и проверьте результат на реальном наборе series.</li><li>Если порог пересечён, найдите один связанный журнал или trace. Сравните время, operation, outcome и correlation ID.</li><li>Только после этого меняйте код, timeout, retry или правило alert. Зафиксируйте критерий отката.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Метрика не видит событие, которое не дошло до точки instrumentation. Ошибка в браузере может произойти до приложения. Пропущенный scrape оставит окно неполным. Перезапуск процесса изменит сырое значение counter, хотя корректная функция запроса умеет учитывать reset. Неправильный label может спрятать нужную операцию в общей series.</p>\n<p>Поэтому ноль ошибок не равен нулю пользовательских проблем. Проверьте, что target жив, series существует, timestamp попадает в окно, а журнал содержит тот же путь. Если условия не выполняются, честный результат — «сигнал недостаточен», а не «система исправна». Это отрицательное решение экономит время и не создаёт ложной уверенности.</p>\n<p>Учебные snapshots и код выше не измеряют память Prometheus, latency, нагрузку, доставку alert или поведение exporter. Они не подтверждают production-результаты. Их задача — показать связь между операционным вопросом, metric type, labels, окном и проверкой. Для реального внедрения нужен отдельный стенд с известным scrape, контролируемым запросом и сохранённым ответом PromQL.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Разбор готов, если другой инженер может повторить его без устной подсказки: назвать series и её labels, объяснить момент увеличения counter, выполнить запрос с указанным окном, увидеть результат на доступном наборе данных и перейти от срабатывания к одному связанному журналу или trace. Дополнительно он должен уметь показать отрицательный путь: почему ноль не закрывает жалобу, если metric не покрывает endpoint или scrape пропущен.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://prometheus.io/docs/practices/instrumentation/' target='_blank' rel='noopener noreferrer'>Prometheus: Instrumentation</a> — официальные рекомендации по счётчикам ошибок, общему числу попыток, выбору типа метрики и осторожности с labels.</li><li><a href='https://prometheus.io/docs/prometheus/latest/querying/functions/' target='_blank' rel='noopener noreferrer'>Prometheus: Query functions</a> — официальное описание <code>increase()</code>, counter reset и расчёта прироста в range vector.</li><li><a href='https://prometheus.io/docs/practices/naming/' target='_blank' rel='noopener noreferrer'>Prometheus: Metric and label naming</a> — официальные правила именования и ограничения для metric dimensions.</li></ul>"
}