P95 в Prometheus: как считать задержку по histogram и не усреднять процентили
У двух реплик API есть собственный P95 задержки. На первой он равен 100 мс, на второй — 2 секундам. Кажется естественным взять среднее и показать пользователю 1,05 секунды. Но первая реплика могла обработать десять тысяч запросов, а вторая — десять. Два числа не содержат информации, необходимой для восстановления общей задержки.
P95 — граница, ниже которой располагается примерно 95% наблюдений. Чтобы объединить реплики, нужно сначала объединить сведения о распределении запросов. Histogram хранит такую информацию через интервалы значений, buckets. После объединения интервалов можно оценить процентиль для всего сервиса.
Как читать накопительные buckets
В classic histogram клиент публикует серии с суффиксом _bucket. Label le задаёт включительную верхнюю границу. Пусть за выбранное окно получились следующие накопительные количества: до 0,1 секунды — 50 запросов, до 0,3 — 90, до 1 — 100 и до +Inf — 100.
Строка с границей 0,3 уже включает первые 50 запросов. Между 0,1 и 0,3 расположено 40 наблюдений, а не 90. Поэтому нельзя складывать все buckets ради общего числа запросов: один запрос попадёт сразу в несколько накопительных границ. Общее количество дают _count или bucket +Inf.
Нужная ранговая позиция равна 0,95 × 100 = 95. Первые 90 запросов укладываются в 0,3 секунды; ещё 10 находятся между 0,3 и 1. Линейная оценка внутри этого интервала даёт 0,3 + (95 − 90) / (100 − 90) × (1 − 0,3) = 0,65 секунды. Это оценка по bucket, а не найденный в журнале запрос длительностью ровно 650 мс.
Соберём распределение сервиса
histogram_quantile(
0.95,
sum by (job, le) (
rate(http_request_duration_seconds_bucket{job="api"}[5m])
)
)
Сначала rate переводит накопительные счётчики в скорость за окно и учитывает рестарты каждой серии. Затем sum объединяет реплики, сохраняя job и границы le. Только после этого вычисляется процентиль. Label instance здесь намеренно исчезает: нужен один P95 для job. Если оставить instance, получите отдельные линии реплик.
При объединении classic histogram границы buckets должны быть согласованы. Если одна версия приложения публикует 0,3 секунды, а другая — только 0,5, результат суммирования отдельных границ перестаёт представлять общее распределение всех запросов. Перед сравнением выкладок проверьте настройку инструментации на каждой версии приложения.
На панели задайте секунды, поскольку имя метрики и границы заданы в секундах. Grafana может автоматически показать миллисекунды для небольших значений. Умножение запроса на тысячу при одновременном выборе секунд создаст неверную подпись. Выберите один согласованный способ отображения.
Что меняется у native histogram
Native histogram передаёт составное histogram-значение, поэтому запрос использует базовое имя метрики, а отдельный label le сохранять не требуется:
histogram_quantile(
0.95,
sum by (job) (
rate(http_request_duration_seconds{job="api"}[5m])
)
)
Выбор выражения определяется фактически сохранённым типом данных. Наличие метрики с похожим именем не доказывает, что в ней native histogram. Проверьте инструментирование, ответ запроса и настройки приёма histogram на своём Prometheus. Если сервер преобразует classic histogram при приёме, ориентируйтесь на получившийся тип, а не только на исходную библиотеку приложения.
Для обычных classic buckets применяется линейная интерполяция. У native histogram со стандартными экспоненциальными buckets интерполяция ненулевых buckets устроена иначе; для custom buckets сохраняется линейная модель. Подробности histogram_quantile полезны, когда важна точность рядом с конкретной границей.
Когда лучше спросить о доле запросов
Если требование сформулировано как «95% запросов быстрее 300 мс», иногда проще проверить долю запросов в bucket 0,3. При наличии точной classic-границы отношение скорости этого bucket к скорости count прямо отвечает на вопрос. P95 полезен для исследования распределения, а доля под порогом — для проверки сформулированного условия.
Наконец, рядом с процентилем покажите интенсивность или число наблюдений за окно. P95 по пяти запросам и по пятистам тысячам описывает разный объём данных. При полном отсутствии наблюдений возможен NaN; превращать его в нулевую задержку значит сообщать, что сервис мгновенно обслужил запросы, которых в окне вообще не было.
Ещё по теме
- Scrape timeout в Prometheus: как различить медленный exporter и проблему сети
- Rate и increase в Prometheus: считаем запросы и учитываем сброс счётчика
Продолжить изучение
Хотите построить мониторинг, который помогает находить причины сбоев? Метрики, дашборды и оповещения входят в программу «Prometheus и Grafana: мониторинг и observability».
Продолжить на практике
- Prometheus и Grafana: мониторинг и observability ↗
Замечайте сбои до жалоб пользователей. Соберите мониторинг приложения: метрики Prometheus, запросы PromQL, дашборды Grafana и алерты, по которым понятно, что делать. Итог — наблюдаемый сервис и разбор учебного инцидента.
