Метрики: количества, скорость и распределения
В главе разобрано, какие числа хранит метрика, чем счётчик отличается от мгновенного значения, почему среднее скрывает редкие задержки и как гистограмма хранит распределение длительностей. Все расчёты сделаны на двадцати настоящих запросах к сервису products-api.
Метрика показывает масштаб проблемы. Что случилось с отдельным запросом, она не хранит.
- Раздел
- Основы наблюдаемости
- Уровень
- Начальный · Python знаком
- Связанные главы
- 1.2 — мониторинг, 1.8 — Prometheus и Grafana
- Зачем это знать
- Правильно читать графики длительности и доли ошибок и не делать из них ложных выводов
§1Измерение и метрика
Если отправить запрос к /products и засечь время от отправки до получения ответа, получится одно измерение, например 85 мс. Тысяча запросов дадут тысячу измерений. Хранить и показывать каждое по отдельности незачем, поэтому их сворачивают в несколько чисел.
Преподаватель поставил за семестр триста оценок. В ведомость группы идут несколько чисел: средний балл, число пропусков, число несданных работ.
Сервис обработал тысячу запросов и получил тысячу измерений длительности. В метрики идут несколько чисел: количество запросов, суммарное время, доля медленных.
Метрика — числовая характеристика работы системы, которую регулярно измеряют и хранят как временной ряд. Метрика описывает совокупность событий или состояние за время. Отдельное событие в ней не видно.
Из тысячи измерений длительности получаются разные метрики: количество запросов, суммарное время, среднее, доля медленных. Какая нужна, зависит от вопроса. Поэтому к названию метрики всегда прилагают описание: что измеряли, в какой точке, в каких единицах.
| Метрика | Единица | Что уточнить |
|---|---|---|
| Завершённые запросы | штуки | Какие маршруты и коды ответа входят в счёт, с какого момента идёт счёт |
| Поток запросов | запросы в секунду | За какое окно посчитана скорость |
| Длительность запроса | секунды или миллисекунды | Измерена на клиенте или в обработчике, входят ли ошибочные запросы |
| Длина очереди | задания | Только ожидающие или вместе с выполняемыми |
Метрики сервиса стенда называются lab_http_requests_total и lab_http_request_duration_seconds. Имена построены по соглашениям Prometheus, поэтому читаются без документации.
- lab
- приставка: какое приложение отдаёт метрику
- _total
- окончание счётчика
- _seconds
- единица измерения в имени
Первоисточник: Prometheus · Metric and label naming ↗
Метрики различаются по тому, как ведёт себя число. Следующий раздел называет три вида.
§2Три вида числовых данных
Числа в метриках ведут себя по-разному, и от этого зависит, какие вычисления с ними допустимы. В Prometheus и большинстве других систем различают три основных вида.
Счётчик (counter) только растёт, а при перезапуске процесса начинается с нуля. Так считают запросы, ошибки, отправленные байты. Мгновенное значение (gauge) растёт и уменьшается. Так показывают длину очереди, занятую память, активные соединения. Распределение (histogram) — это набор счётчиков по диапазонам значений. Так хранят длительность запросов и размер ответов.
Вид выбирают по тому, что измеряют. События считают счётчиком: число запросов может только расти. Состояние в момент измерения показывают мгновенным значением: очередь то растёт, то убывает. Если важно, сколько запросов быстрые и сколько медленные, берут гистограмму.
Первоисточник: Prometheus · Metric types ↗
Первый из трёх видов — счётчик. С него начинается подробный разбор.
§3Счётчик и скорость
Счётчик (counter) — метрика, которая хранит накопленное количество событий с момента запуска процесса. Значение только увеличивается, а при перезапуске процесса начинается с нуля.
Само значение счётчика почти ничего не говорит. Число 15 000 означает, что с момента запуска процесс обработал 15 000 запросов. Процесс мог работать минуту или месяц, так что о текущей нагрузке по этому числу судить нельзя.
Пользу даёт изменение счётчика: берут два значения с интервалом 10 секунд и делят прирост на интервал.
- 15 240
- значение счётчика в 12:00:10
- 15 000
- значение счётчика в 12:00:00
- 10 с
- интервал между двумя чтениями
before = 15000 # значение счётчика в 12:00:00
after = 15240 # значение счётчика в 12:00:10
seconds = 10
print((after - before) / seconds, "запроса/с")24.0 запроса/сЭто средняя скорость за 10 секунд. Внутри интервала запросы могли приходить неравномерно: 200 в первую секунду и 40 за остальные девять. Счётчик этого не покажет, но и не потеряет ни одного события: сколько бы запросов ни пришло между чтениями, прирост их учтёт.
Перезапуск процесса между измерениями
В 12:00:00 счётчик равен 15 000. В 12:00:04 процесс перезапустили, и счёт начался заново. В 12:00:10 счётчик равен 90.
−1491 запроса/с
(90 − 15 000) / 10. Отрицательной скорости не бывает.
не меньше 9 запросов/с
Счётчик уменьшился, значит, был сброс, и прирост не меньше 90. Это нижняя оценка: запросы до сброса не видны.
Поэтому системы мониторинга считают так: если счётчик уменьшился, процесс перезапускался, и новое значение прибавляется к старому. Так учитывают сбросы функции rate и increase в Prometheus.
На дашборде Grafana в главе 1.8 две верхние панели показывают счётчики с запуска, а панель «Поток запросов» — скорость, посчитанную из того же счётчика. Метрика одна, а вопросы разные: «сколько всего» и «сколько в секунду сейчас».
Счётчик считает события. Для состояний нужен второй вид метрики.
§4Мгновенное значение
Мгновенное значение (gauge) — метрика, которая показывает текущее состояние в момент измерения. Значение может и расти, и уменьшаться.
По-английски gauge — измерительный прибор со стрелкой, например манометр или указатель уровня топлива. Стрелка показывает, сколько есть сейчас, и может двигаться в обе стороны.
Обращаться с мгновенным значением как со счётчиком нельзя. В 12:00 в очереди 40 заданий, в 12:01 — 35. Разница −5 не означает, что за минуту обработали пять заданий: могло прийти 100 новых и выполниться 105. Мгновенное значение показывает только остаток, а сколько заданий прошло через очередь, по нему не узнать.
| Вопрос об очереди | Какая метрика отвечает |
|---|---|
| Сколько заданий ждёт сейчас | Мгновенное значение: длина очереди |
| Сколько заданий поступило за минуту | Счётчик поступивших заданий |
| Сколько заданий выполнено за минуту | Счётчик выполненных заданий |
| Успевают ли обработчики | Сравнение скоростей двух счётчиков или рост длины очереди |
Второе отличие: мгновенное значение фиксирует состояние только в момент измерения. Если очередь выросла до 5000 и опустела между двумя измерениями, пика на графике не будет. У счётчика такой проблемы нет, поэтому события считают счётчиком, а мгновенное значение оставляют для состояний.
Счётчик и мгновенное значение разобраны на примерах. Для третьего вида, распределения, нужны настоящие измерения, и следующий раздел их получает.
§5Двадцать запросов к стенду
Теперь измерения со стенда. Скрипт experiment.py из архива отправляет двадцать запросов подряд и сохраняет длительность каждого. Сценарии заданы заранее: запросы 7 и 17 медленные, запрос 13 завершается ошибкой, остальные семнадцать быстрые. Это управляемый опыт: настоящий трафик он не представляет, но его можно повторить и проверять на нём расчёты.
Исходные данные можно проверить самим: requests.csv ↓ содержит все двадцать строк, summary.json ↓ — итоговый расчёт. Времени в таблице два. client_ms измерил скрипт-клиент, handler_ms — сам сервис внутри обработчика. Все расчёты ниже сделаны по времени клиента: его ждёт пользователь.
import csv
import math
import statistics
with open("results/requests.csv", newline="") as file:
durations = sorted(float(row["client_ms"]) for row in csv.DictReader(file))
n = len(durations)
print("запросов:", n)
print("среднее:", round(statistics.mean(durations), 3), "мс")
print("медиана:", round(statistics.median(durations), 3), "мс")
print("p95:", durations[math.ceil(0.95 * n) - 1], "мс")запросов: 20
среднее: 379.47 мс
медиана: 85.739 мс
p95: 3024.239 мсПрограмма читает CSV модулем csv: DictReader превращает каждую строку в словарь с ключами из заголовка. Генератор берёт из каждой строки client_ms и переводит в число, а sorted упорядочивает длительности по возрастанию. Функции statistics.mean и statistics.median считают среднее и медиану. Как устроена последняя строка с p95, разобрано ниже.
В итогах прогона среднее в четыре раза больше медианы. Следующий раздел объясняет, откуда такая разница.
§6Почему среднее скрывает редкие задержки
Девять поездок заняли по 20 минут, одна из-за аварии на дороге заняла 3 часа. Среднее время поездки — 36 минут, хотя ни одна поездка столько не длилась.
Восемнадцать запросов заняли около 85 мс, два — около 3 секунд. Среднее — 379 мс, хотя ни один запрос столько не длился.
Восемнадцать запросов из двадцати заняли от 78 до 94 мс. Два запроса длились около 3 секунд. Среднее получилось 379 мс, хотя ни одного запроса такой длительности в прогоне нет.
Так получается потому, что среднее складывает все значения. Два запроса по 3000 мс добавляют к сумме около 6000 мс, и после деления на 20 каждый обычный запрос как будто стал медленнее на 300 мс. Без этих двух запросов среднее оставшихся восемнадцати — около 85 мс.
Медиана — значение, которое делит упорядоченный набор пополам: половина значений меньше медианы, половина больше. При чётном числе значений медиана равна среднему двух центральных.
Медиана прогона — 85,739 мс, и она хорошо описывает типичный запрос. Но у неё обратный недостаток: медленные запросы она не замечает совсем. Если бы медленным стал не каждый десятый, а каждый третий запрос, медиана осталась бы около 86 мс.
| Показатель | Значение | Что описывает | Чего не видит |
|---|---|---|---|
| Среднее | 379 мс | Суммарные затраты времени | Форму распределения: редкие долгие и частые короткие запросы смешиваются |
| Медиана | 86 мс | Типичный запрос | Медленную часть запросов |
| p95 | 3024 мс | Границу медленных 5% запросов | Что происходит у самых быстрых |
Среднее всё же нужно. Чтобы оценить, сколько процессорного времени сервис тратит на запросы, считают сумму или среднее. А для вопроса «насколько долго ждут пользователи» одного среднего мало.
В таблице есть третий показатель, p95. Он описывает медленную часть запросов, и ему посвящён следующий раздел.
§7Перцентиль
Перцентиль — значение, ниже которого лежит заданный процент упорядоченных наблюдений. p95 — такая длительность, что 95% запросов выполнились не дольше неё, а 5% — дольше.
p95 читается как «95-й перцентиль». Часто используют p50 (это медиана), p90, p95 и p99. Чем больше число, тем более редкие медленные запросы описывает перцентиль.
Посчитать перцентиль можно разными методами. Здесь используется метод ближайшего ранга: он проще остальных.
- 0,95
- уровень перцентиля: p95
- n
- число значений в выборке
- ceil
- округление вверх до целого
Значения упорядочивают по возрастанию и берут 19-е, считая с единицы. В Python нумерация с нуля, поэтому в коде выше стоит индекс math.ceil(0.95 * n) - 1.
Ниже те же двадцать запросов. Сначала столбцы стоят в порядке отправки. Переключите на «По длительности»: столбцы выстроятся по возрастанию, и 19-й столбец окажется p95.
Кнопки меняют порядок столбцов. Нажмите на столбец, чтобы увидеть номер запроса, длительность и код ответа. Данные — файл requests.csv записанного прогона.
В порядке отправки медленные запросы 7 и 17 стоят в середине. После сортировки они оказываются последними, а ошибочный запрос 13 — первым: ошибка вернулась быстрее всех, за 78 мс. Ошибочные запросы в выборке длительности тянут показатели вниз, и сбой может выглядеть как ускорение.
Другие методы вычисляют перцентиль с интерполяцией между соседними значениями, и результаты немного расходятся. На больших выборках разница мала, а на двадцати значениях заметна, поэтому рядом с перцентилем указывают метод.
Перцентиль посчитан по списку из двадцати значений. Следующий раздел показывает, как его получают, когда значений миллионы.
§8Гистограмма: как хранят распределение
На стенде перцентиль посчитан по списку всех длительностей. Сервис с тысячей запросов в секунду так делать не может: список всех значений занял бы слишком много места. Нужно сохранить форму распределения в нескольких числах.
Почта не записывает вес каждой посылки. Она ведёт несколько счётов: посылки «до 1 кг», «до 5 кг» и «любого веса». Посылка весом 800 граммов засчитывается во все три.
Сервис не хранит длительность каждого запроса. Он ведёт несколько счётчиков: запросы «не дольше 0,1 с», «не дольше 0,25 с» и так далее. Быстрый запрос засчитывается во все.
Гистограмма (histogram) — метрика, которая делит возможные значения на диапазоны и считает, сколько наблюдений попало в каждый. Диапазоны называют корзинами (buckets).
Сервис отдаёт гистограмму длительности по адресу /metrics. Так выглядит ответ в браузере после нескольких сотен запросов.
Формат экспозиции Prometheus: строки комментариев начинаются с #, каждая строка данных — имя метрики, метки в фигурных скобках и значение.
- # HELP. описание метрики для человека.
- Счётчик запросов. два ряда: 254 ответа 200 и 11 ответов 500.
- # TYPE histogram. дальше идут строки гистограммы длительности.
- Корзины _bucket. сколько запросов уложилось в границу
le, в секундах. - le="+Inf". последняя корзина: все запросы.
- _sum и _count. сумма всех длительностей в секундах и число наблюдений.
# HELP — описание метрики для человека, # TYPE — её вид: counter или histogram. Первые две строки данных — счётчик запросов с разбивкой по статусу: 254 успешных и 11 ошибочных.
Дальше идёт гистограмма. Строки _bucket — счётчики корзин. Метка le означает less or equal, «меньше или равно». Корзина le="0.1" хранит число запросов длительностью не больше 0,1 секунды. Корзины накопительные: каждая включает все предыдущие, поэтому числа сверху вниз только растут.
Верхний ряд — то, что хранит гистограмма: каждая корзина считает все запросы не длиннее своей границы, поэтому числа только растут. Нижний ряд получен вычитанием соседних корзин и отвечает на вопрос «сколько запросов попало в сам диапазон»: 246 запросов уложились в 100 мс, ещё 19 длились от 2,5 до 5 секунд, между этими границами не оказалось ни одного. Числа взяты со снимка /metrics выше.
| Корзина | Значение | Что означает |
|---|---|---|
| le="0.1" | 246 | 246 запросов уложились в 100 мс |
| le="0.25" … le="2.5" | 246 | Между 100 мс и 2,5 с не попал ни один запрос |
| le="5.0" | 265 | 265 − 246 = 19 запросов длились от 2,5 до 5 секунд |
| le="+Inf" | 265 | Все запросы; совпадает с _count |
Последние две строки — _sum и _count: сумма всех длительностей в секундах и общее число наблюдений. Из них получается среднее: 77,73 / 265 ≈ 0,293 с, или 293 мс. Так построена панель «Средняя длительность» в Grafana.
Оценка p95 по корзинам
Отдельных длительностей гистограмма не хранит, поэтому перцентиль по ней можно только оценить. p95 соответствует позиции 0,95 × 265 = 251,75. В корзине до 2,5 с лежат 246 запросов, в корзине до 5 с — 265, значит, позиция 251,75 попадает в диапазон от 2,5 до 5 секунд.
- 2,5 и 5
- нижняя и верхняя граница корзины, в секундах
- 251,75
- позиция p95 среди 265 запросов
- 246 и 265
- значения соседних накопительных корзин
Внутри корзины значения считаются распределёнными равномерно. Настоящие медленные запросы длятся около 3,02 с: оценка ошиблась на четверть секунды, потому что корзина широкая.
Точность перцентилей из гистограммы задают границы корзин. Границы делают гуще там, где важна точность. Если обещанная длительность ответа 300 мс, нужны корзины 0,2, 0,3 и 0,4 с: одной корзины от 0,25 до 0,5 недостаточно. Функция histogram_quantile в Prometheus выполняет ту же линейную оценку.
Первоисточник: Prometheus · Histograms and summaries ↗
Гистограмма хранит распределение одного экземпляра. Следующий раздел показывает, как объединять данные нескольких экземпляров.
§9Почему перцентили нельзя усреднять
У сервиса три экземпляра, каждый считает свой p95, а на общей панели нужен p95 всего сервиса. Взять среднее от трёх значений — неверный расчёт.
Два экземпляра с разной нагрузкой
| Экземпляр | Запросов | p95 |
|---|---|---|
| api-1 | 9 900 | 100 мс |
| api-2 | 100 | 5 000 мс |
| Среднее двух p95 | — | 2 550 мс |
Среднее даёт 2 550 мс, как будто медленных запросов много. В действительности из 10 000 запросов медленные только на api-2, их не больше сотни, то есть около 1%. Настоящий общий p95 близок к 100 мс. Среднее перцентилей дало экземплярам одинаковый вес, хотя нагрузка отличается в 99 раз.
Правильно сначала объединить распределения, а потом посчитать перцентиль. Гистограммы объединяются сложением: корзины с одинаковыми границами складывают по всем экземплярам и получают гистограмму сервиса. Это ещё одна причина хранить длительности гистограммой: готовые перцентили объединить нельзя.
Метрики сжимают события в числа. Последний раздел подводит итог: что при этом сохраняется и что теряется.
§10Что теряется при агрегации
Метрика сжимает много событий в несколько чисел, и подробности при этом теряются. Сравнение ниже показывает, что остаётся и что теряется.
Масштаб
- За пять минут 5% запросов вернули 500.
- 19 запросов длились от 2,5 до 5 секунд.
- На api-2 ошибок больше, чем на api-1.
Подробности
- Текст исключения каждого ошибочного запроса.
- Какие это были запросы и на каком этапе ушло время.
- Какие пользователи получили ошибку.
Для левой колонки метрики подходят лучше других сигналов: они занимают мало места и быстро считаются даже за месяцы. Для правой нужны другие сигналы: текст исключения хранится в логах, этапы запроса — в трассах. Метрика показывает, что и насколько изменилось, а дальше расследование переходит к подробным данным.
§11Частые ошибки
| Что делают | Что получается | Как правильно |
|---|---|---|
| Выводят на панель значение счётчика с подписью «запросов в минуту» | Число только растёт и не отражает текущую нагрузку | Показывать скорость, посчитанную из прироста счётчика |
| Вычитают соседние значения счётчика без учёта сброса | После перезапуска получается отрицательная скорость | Использовать функции, которые учитывают сбросы, например rate |
| Считают разность мгновенных значений числом событий | Изменение длины очереди принимают за число обработанных заданий | Для событий заводить отдельные счётчики |
| Оценивают задержки только по среднему | Редкие долгие запросы размываются | Смотреть медиану и старшие перцентили вместе |
| Усредняют перцентили разных экземпляров | Результат не соответствует ни одному реальному распределению | Складывать корзины гистограмм и считать перцентиль по сумме |
| Выбирают корзины гистограммы без учёта важных порогов | Перцентили оцениваются с большой ошибкой | Делать границы гуще около обещанной длительности |
§12Проверьте себя
Счётчик запросов в 10:00:00 равен 8 000, в 10:00:30 — 8 450. Какова средняя скорость?
(8 450 − 8 000) / 30 = 15 запросов в секунду.
Длина очереди в 10:00 была 200, в 10:05 — 180. Можно ли сказать, что за пять минут обработано 20 заданий?
Нельзя. Длина очереди — мгновенное значение. За пять минут могло поступить и выполниться любое число заданий; изменение показывает только разницу остатков.
В выборке 40 длительностей. Какая позиция отсортированного списка даёт p95 по методу ближайшего ранга?
ceil(0,95 × 40) = ceil(38) = 38-я позиция при счёте с единицы.
Гистограмма: le="0.5" — 900, le="1.0" — 980, le="+Inf" — 1000. Сколько запросов длились больше секунды?
1000 − 980 = 20 запросов. Корзины накопительные, поэтому число запросов в диапазоне — разность соседних корзин.
Почему ошибочный запрос 13 оказался самым быстрым в прогоне?
Сценарий error прерывает обработку исключением после ожидания зависимости и не выполняет этап serialize. Ответ с ошибкой возвращается раньше, чем полный успешный ответ.
§13Шпаргалка
- Метрика — числовая характеристика работы системы, хранимая как временной ряд.
- Счётчик только растёт; полезна его скорость, сбросы при перезапуске надо учитывать.
- Мгновенное значение показывает состояние; разность двух значений не равна числу событий.
- Среднее смешивает редкие долгие и частые короткие запросы.
- Медиана описывает типичный запрос, старшие перцентили — медленную часть.
- Метод ближайшего ранга: позиция ceil(p × n) в отсортированном списке.
- Гистограмма хранит накопительные счётчики корзин, сумму и количество.
- Перцентиль по гистограмме — оценка, её точность задают границы корзин.
- Перцентили разных экземпляров не усредняют; складывают корзины.