8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"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>До выбора типа метрики запишите наблюдаемый симптом без диагноза: какой пользовательский путь нарушен, в какое время это происходит и какое действие нельзя делать вслепую. В нашем примере нужно ответить на узкий вопрос: сколько завершённых запросов checkout закончилось ошибкой за последние десять минут?</p>\n<p>Такой вопрос сразу задаёт границы. Событие — завершённый запрос с известным исходом. Операция — checkout. Окно — десять минут. Следующий шаг после сигнала — открыть журнал или trace, а не объявить причину найденной. Если поменять любую из этих границ, число перестанет отвечать на исходный вопрос.</p>\n<h2>Отделите событие от состояния</h2>\n<p>Для завершённых запросов подходит <em>counter</em> — накопительная метрика событий. Она растёт и может сброситься при перезапуске процесса. Для вопроса «сколько ошибок checkout появилось за десять минут» нужен прирост counter, а не его последняя сырая величина.</p>\n<p>Для вопроса «сколько запросов выполняется сейчас» нужен <em>gauge</em>: он описывает текущее состояние и может расти или уменьшаться. Для вопроса «как распределилась длительность» нужна отдельная метрика наблюдений, например <em>histogram</em>. Не называйте один тип другим только потому, что все они отображаются линиями.</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="checkout", outcome="error"}[10m])) >= 2</code></pre>\n<p>Выражение выбирает series с двумя ограниченными labels, считает прирост за десять минут и сравнивает его с учебной границей. Оно не сообщает причину ошибки. После срабатывания нужно открыть связанный журнал, trace или изолированный сценарий.</p>\n<p><code>increase()</code> учитывает reset counter после перезапуска target и рассчитывает прирост на указанном диапазоне. Поэтому ручная разность двух крайних snapshots полезна для объяснения идеи, но не должна подменять выполнение PromQL. На результат влияют время samples, scrape interval, пропуски scrape и границы range vector.</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="checkout", outcome="error"}\n\n# Плохая граница: уникальный label разрушает агрегацию\nstore_http_requests_total{operation="checkout", request_id="8f2d..."}</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="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 пересечена.</code></pre>\n<p>Повторить разбор можно в таком порядке: зафиксировать ту же series, подставить три значения в выбранное окно, проверить арифметику <code>2 - 0 = 2</code>, затем выполнить PromQL на доступном сервере метрик. Отдельно повторите пример с последним значением <code>1</code>: он не должен пересекать границу <code>>= 2</code>. Это проверяет учебную логику, но не проверяет exporter, scrape или alert delivery.</p>\n<p>В настоящем Prometheus <code>increase()</code> работает по samples range vector, а не по обещанию ровно трёх точек. Она корректирует reset counter и экстраполирует расчёт на границы окна. Поэтому простая разность полезна как контрольный пример, но не как формула для ручного production-расчёта.</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-результаты. Их задача — показать связь между операционным вопросом, типом метрики, 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><li><a href='https://prometheus.io/docs/concepts/metric_types/' target='_blank' rel='noopener noreferrer'>Prometheus: Metric types</a> — определения counter, gauge и histogram, использованные в сравнении типов метрик.</li></ul>"
|
||
}
|