Что такое перцентили, как их считать, чем они лучше среднего, p50/p95/p99/p999, tail latency, гистограммы и практика измерений.

Перцентили — подробно

Среднее — это то, что никто не чувствует. Перцентили — это то, что чувствуют почти все.

Когда инженер говорит «сервис отвечает за 100 мс», он почти всегда имеет в виду среднее время отклика. И это почти всегда вводит в заблуждение. Среднее скрывает распределение: оно «размазывает» редкие, но катастрофически медленные запросы по всей выборке и делает вид, что всё хорошо.

Перцентили решают эту проблему: они показывают не «типичное значение», а границу, ниже которой укладывается заданная доля запросов. Именно они позволяют увидеть правду о том, как система ведёт себя для реальных пользователей.

Что такое перцентиль: определение

Перцентиль (процентиль) — это значение, ниже которого находится заданный процент наблюдений в упорядоченной выборке.

Формально: p-й перцентиль — это такое число x, что доля значений, меньших или равных x, составляет не менее p%.

p50  →  50% значений меньше или равны этому числу
p95  →  95% значений меньше или равны этому числу
p99  →  99% значений меньше или равны этому числу
p999 → 99.9% значений меньше или равны этому числу

Важно: перцентиль — это не среднее и не медиана в строгом смысле (хотя p50 и медиана часто совпадают). Это точка отсечения распределения.

Как считать перцентиль: пошагово

Возьмём простой пример. Есть 10 запросов со временем отклика (в миллисекундах):

12, 15, 18, 20, 22, 25, 30, 40, 80, 500

Шаг 1. Отсортируем значения по возрастанию — они уже отсортированы.

Шаг 2. Найдём позицию нужного перцентиля. Есть несколько способов, но самый простой — индекс:

index = ceil(p / 100 * N) - 1

где N — количество значений, p — перцентиль

Для p50 при N = 10:

index = ceil(0.50 * 10) - 1 = ceil(5) - 1 = 4
значение = 22 мс

Для p95:

index = ceil(0.95 * 10) - 1 = ceil(9.5) - 1 = 10 - 1 = 9
значение = 500 мс

Для p99 при N = 10:

index = ceil(0.99 * 10) - 1 = ceil(9.9) - 1 = 10 - 1 = 9
значение = 500 мс

Обрати внимание: при малых выборках p95 и p99 могут совпадать — это нормально, и именно поэтому для честных измерений нужно много данных.

Почему среднее врёт

Посчитаем среднее для того же набора:

(12 + 15 + 18 + 20 + 22 + 25 + 30 + 40 + 80 + 500) / 10 = 76.2 мс

Среднее — 76 мс. Но девять из десяти запросов быстрее 80 мс, а медиана — всего 22 мс. Среднее «вытянул» один-единственный запрос на 500 мс.

Именно поэтому среднее — плохая метрика для времени отклика: оно не отражает опыт ни одного реального пользователя.

Среднее — это значение, которого не испытывает почти никто. Перцентиль — это значение, которое испытывают почти все.

Основные перцентили и что они означают

  • p50 (медиана) — половина запросов быстрее, половина медленнее. Хорошая метрика «типичного» опыта. Если p50 растёт — что-то системно не так.
  • p90 — 90% запросов быстрее. Начинает показывать «второй эшелон» пользователей.
  • p95 — 95% запросов быстрее. Стандартная метрика в большинстве SLO. 5% пользователей уже испытывают проблемы.
  • p99 — 99% запросов быстрее. Именно здесь живут самые медленные пользователи, и именно они чаще всего уходят. Для систем с высокой нагрузкой это ключевая метрика.
  • p999 (99.9%) — 0.1% самых медленных запросов. Важно для систем, где запросов очень много: даже редкие выбросы складываются в заметную долю общего времени.

Практическое правило: чем выше нагрузка и чем больше пользователей, тем важнее высокие перцентили. Если у сервиса 10 миллионов запросов в день, то p999 — это 10 000 медленных запросов ежедневно. Это уже не «редкий случай».

Почему правда живёт в хвосте

Распределение времени отклика почти всегда имеет длинный правый хвост: большинство запросов быстрые, но небольшая доля — очень медленные. Причины разные:

  • сборка мусора в JVM или Go;
  • очереди на стороне БД;
  • сетевые задержки и потери пакетов;
  • конкуренция за блокировки;
  • холодный кэш;
  • фоновые задачи, «съедающие» CPU.

Эти события редки, но их эффект на конкретного пользователя огромен. И именно они определяют, останется ли пользователь доволен или уйдёт к конкуренту.

Хвост распределения — самая интересная его часть. Именно там принимаются решения о том, вернётся ли пользователь.

Усиление хвостовой задержки (Tail Latency Amplification)

Отдельная ловушка — когда один пользовательский запрос внутри системы вызывает несколько сервисов параллельно. Даже если каждый сервис сам по себе имеет хороший p99, вероятность, что хотя бы один из вызовов окажется медленным, резко растёт с их количеством.

Формула:

P(хотя бы один медленный) = 1 - (1 - p)^n

где p — вероятность медленного ответа одного сервиса,
    n — количество параллельных вызовов

Пример при p = 1%:

n = 1    →  1%
n = 10   →  ~9.6%
n = 50   →  ~39.5%
n = 100  →  ~63.4%
n = 200  →  ~86.6%

То есть если страница собирается из 100 внутренних вызовов, и у каждого p99 = 1%, то больше половины пользовательских запросов будут испытывать хотя бы одну медленную операцию. Это и есть tail latency amplification.

Отсюда практические выводы:

  • ограничивать fan-out — не дёргать по 100 сервисов на запрос;
  • использовать таймауты и fallback — лучше отдать частичный ответ, чем ждать;
  • применять hedging — дублировать медленные запросы и брать первый ответ;
  • изолировать медленные вызовы, чтобы они не «съедали» общий бюджет времени.

Как измерять перцентили на практике

Есть три подхода, и у каждого — свои компромиссы.

1. Точное вычисление (полная выборка). Хранить все значения и считать перцентиль по отсортированному массиву. Точно, но дорого по памяти и CPU при большом потоке данных.

2. Гистограммы с фиксированными корзинами. Разбить диапазон значений на интервалы (например, 0–10 мс, 10–20 мс, …) и считать попадания. Дёшево, но точность ограничена размером корзины. Так работают, например, Prometheus histograms.

3. Алгоритмы со сжатием (t-digest, HDR Histogram, DDSketch). Дают высокую точность на хвостах при малом объёме памяти. Это стандарт для production-мониторинга времени отклика.

Важно помнить: перцентили нельзя усреднять. Нельзя взять p99 за 10 минут и усреднить их, чтобы получить p99 за час. Это математически некорректно. Нужно либо агрегировать сами гистограммы, либо считать перцентиль по объединённой выборке.

p99(p99(x), p99(y)) ≠ p99(x ∪ y)

Пример: как читать метрики

Допустим, у нас есть отчёт за сутки:

p50  = 25 мс
p95  = 80 мс
p99  = 220 мс
p999 = 1500 мс
mean = 40 мс

Что это значит:

  • половина пользователей получает ответ быстрее 25 мс — это хорошо;
  • 5% ждут больше 80 мс — терпимо;
  • 1% ждёт больше 220 мс — уже заметно;
  • 0.1% ждёт больше 1.5 секунды — это те, кто может уйти;
  • среднее 40 мс — красивая цифра, за которой прячется проблема хвоста.

Если SLO сформулирован как «p99 < 200 мс», то система его нарушает, хотя среднее выглядит прекрасно. Это и есть главный аргумент в пользу перцентилей.

Перцентили в контексте SLO/SLA

Именно перцентили ложатся в основу измеримых целей:

SLI:  p99 времени отклика = 220 мс
SLO:  p99 должен быть < 200 мс   (внутренняя цель)
SLA:  гарантируем p99 < 300 мс  (обещание клиенту)

Обрати внимание: SLO и SLA обычно формулируются именно в терминах перцентилей, а не среднего. Потому что среднее не отражает опыт реальных пользователей, а перцентиль — отражает.

Что запомнить

  • Перцентиль — это граница, ниже которой укладывается заданная доля значений.
  • Среднее скрывает выбросы; перцентили — показывают.
  • p50 — типичный опыт, p95/p99 — опыт «страдающих» пользователей, p999 — редкие, но важные случаи.
  • Чем больше внутренних вызовов на запрос, тем сильнее усиливается хвостовая задержка.
  • Перцентили нельзя усреднять — нужно агрегировать гистограммы или считать по объединённой выборке.
  • SLO и SLA формулируются в терминах перцентилей, а не среднего.