Почему среднее время ответа врёт и что показывают перцентили
Дашборд показывает «среднее время ответа — 120 мс», всё зелёное, а в поддержку одно за другим прилетают жалобы «у меня всё виснет». Знакомая ситуация: среднее и реальный опыт пользователя расходятся, потому что среднее арифметическое — плохой инструмент для описания времени отклика. Разберёмся, почему так происходит и что вместо него показывают перцентили p50, p95 и p99.
Содержание
- Что на самом деле измеряет среднее время ответа
- Как один медленный запрос почти не двигает среднее
- Что такое перцентили и как их читать
- Как перцентили считают на практике: гистограммы и подводные камни
- Откуда берутся выбросы, которые прячет среднее
- Что менять в мониторинге: смотреть на перцентили, а не на среднее
Что на самом деле измеряет среднее время ответа
Среднее время ответа — это сумма всех задержек, делённая на количество запросов. Формула честная, но у неё есть скрытое допущение: она хорошо описывает данные, которые более-менее равномерно распределены вокруг одного значения. Время отклика сервера так не работает.
В реальности задержки запросов распределены неравномерно и «скошены вправо»: подавляющее большинство запросов укладывается в узкий диапазон (скажем, десятки миллисекунд), а небольшая часть улетает далеко вправо — в секунды и даже десятки секунд. Причины такого выброса разные: блокировка на диске, пауза сборщика мусора, холодный кэш, медленный запрос к базе, чужая нагрузка на соседней виртуалке. У такого распределения нет «типичного» значения в привычном смысле — и среднее пытается его придумать, размазывая редкие тяжёлые случаи по всей массе быстрых запросов.
Результат: одно число «среднее время ответа» одновременно описывает и типичного пользователя, у которого всё быстро, и того несчастного, кто десять секунд ждал загрузку страницы. Оно физически не может честно представлять обоих — и в итоге не представляет толком ни одного.
Как один медленный запрос почти не двигает среднее
Вот иллюстрация — не результат замера конкретного сервера, а просто арифметика, чтобы показать механизм. Пусть за минуту сервер обработал 10 000 запросов. 9 999 из них уложились примерно в 50 мс. Один запрос — например, из-за блокировки на запись в базе — обработался за 30 секунд (30 000 мс).
Считаем среднее:
(9 999 × 50 + 1 × 30 000) / 10 000 =
(499 950 + 30 000) / 10 000 ≈
529 950 / 10 000 ≈ 53 мс
Среднее сдвинулось с 50 до 53 мс — на 6%. Дашборд по-прежнему покажет «в норме», алерт с порогом «среднее выше 100 мс» даже не дрогнет. А человек, которому этот единственный запрос достался, увидел зависшую страницу на 30 секунд и, скорее всего, ушёл или написал в поддержку.
Это ключевой механизм, из-за которого среднее «врёт»: чем больше общий объём трафика, тем сильнее редкие тяжёлые запросы растворяются в массе быстрых. При миллионе запросов в сутки один запрос-выброс на 30 секунд не сдвинет среднее вообще никак — оно останется на третьем знаке после запятой того же значения. Но если таких выбросов не один, а сотня или тысяча в сутки — для затронутых пользователей это никакая не статистическая погрешность, а вполне реальный, повторяющийся плохой опыт.
Здесь важно понимать: дело не в «злом среднем», которое специально что-то скрывает. Дело в том, что одно число просто не способно передать форму распределения — сколько запросов были быстрыми, сколько медленными и насколько медленными были самые тяжёлые. Для этого нужен другой инструмент.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЧто такое перцентили и как их читать
Перцентиль — это значение, ниже которого находится определённый процент наблюдений в отсортированном наборе данных. Применительно к времени отклика это звучит так: «p95 = 200 мс» значит «95% запросов ответили быстрее 200 мс» (и, соответственно, 5% — медленнее).
Три перцентиля, на которые обычно смотрят:
- p50 (медиана) — середина отсортированного списка задержек. Половина запросов быстрее этого значения, половина медленнее. Хорошо описывает «типичный» опыт, но полностью игнорирует хвост распределения.
- p95 — значение, ниже которого укладываются 95% запросов. Показывает, что происходит с не самым удачливым, но всё ещё массовым сегментом пользователей — теми, кому просто не повезло попасть на не самый быстрый ответ.
- p99 — 99% запросов быстрее этого значения, 1% — медленнее. Это уже прицельно «хвост»: те самые случаи блокировок, холодного кэша, соседей по диску. При заметном трафике даже 1% — это тысячи реальных сессий в сутки.
Разница с процедурой расчёта среднего принципиальная: перцентиль строится на отсортированном ряде значений, а не на их сумме. Поэтому один экстремальный выброс не «размазывается» по всей выборке, а честно занимает своё место в хвосте — и как только доля таких запросов приближается к выбранному порогу (1% для p99, 5% для p95), перцентиль это немедленно показывает, тогда как среднее по-прежнему будет спокойным.
Возвращаясь к примеру выше: если бы медленных запросов было не 1 из 10 000, а 100 из 10 000 (то есть ровно 1%), p99 сразу показал бы значение, близкое к 30 000 мс — потому что именно на границе 99-го процентиля и находятся эти самые тяжёлые запросы. Среднее при этом всё ещё поднялось бы не катастрофически: (9900 × 50 + 100 × 30000) / 10000 ≈ 349,5 мс — заметнее, чем в первом примере, но всё равно на порядок меньше, чем то, что реально ощутили 100 пользователей из десяти тысяч.
Как перцентили считают на практике: гистограммы и подводные камни
В live-системе нельзя буквально «отсортировать все запросы за сутки» — это дорого по памяти. Поэтому системы мониторинга обычно считают перцентили через гистограммы: время ответа раскладывается по заранее заданным корзинам (buckets) — например, «до 10 мс», «до 50 мс», «до 100 мс», «до 500 мс», «до 1 с», «до 5 с» и так далее. Перцентиль потом вычисляется приближённо, интерполяцией внутри той корзины, куда попадает нужная граница.
В Prometheus это тип метрики histogram, а перцентиль из неё достаётся функцией histogram_quantile:
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
Здесь есть несколько практических нюансов, о которые легко споткнуться:
- Границы корзин задаются заранее. Если все ваши бакеты — «10 мс, 50 мс, 100 мс, 500 мс», а реальный p99 находится между 100 мс и 500 мс, точность интерполяции внутри такого широкого интервала будет грубой. Бакеты нужно подбирать под реальный диапазон задержек вашего сервиса.
- Перцентили нельзя усреднять между инстансами. Если у вас три бэкенда с p95 = 100 мс, 150 мс и 400 мс, «средний p95» по трём числам — это не p95 всего трафика. Правильно агрегировать нужно исходные бакеты гистограммы (как в запросе выше — через
sum by (le)), а уже потом считать квантиль по объединённым данным. - Окно агрегации имеет значение. p99 за последнюю минуту и p99 за последний час — разные вещи. Короткое окно чувствительнее к отдельным всплескам, длинное — сглаживает их и может замаскировать разовый, но неприятный инцидент.
Если вы разворачиваете такой стек с нуля на собственном сервере, разумно сразу заложить связку Prometheus и Grafana — она даёт готовые панели с квантилями из коробки, не приходится изобретать расчёт вручную. Мы разбирали, как их связать между собой, в статье Prometheus и Grafana: связка и настройка.
Откуда берутся выбросы, которые прячет среднее
Полезно понимать не только «что» показывает p99, но и «почему» вообще появляются тяжёлые запросы — иначе перцентиль превращается в число без объяснения. Типичные источники хвостовой задержки на сервере:
- Пауза сборщика мусора в managed-рантаймах (Java, .NET, отчасти Node.js и Go) — приложение на короткое время буквально «замирает», и все запросы, попавшие на этот момент, получают задержку выше обычной.
- Блокировки в базе данных — долгая транзакция держит лок, и все запросы, которым нужна та же строка или таблица, встают в очередь.
- Холодный кэш — первый запрос после рестарта сервиса или очистки кэша идёт напрямую в базу или на диск и закономерно медленнее прогретых повторов.
- Диск, а не сеть — операция записи лога, временного файла или сессии упирается в медленный или перегруженный диск; на VPS это особенно заметно на сетевых или сильно переподписанных хранилищах.
- «Шумный сосед» на виртуализации — соседняя виртуальная машина на том же физическом хосте выедает CPU или I/O, и ваши запросы получают задержку не из-за вашего кода, а из-за чужой нагрузки. Разобраться, ваш ли это случай, помогает метрика steal time — мы писали о ней в статье Steal time: как понять, что сосед ест ваш CPU.
- Ретрансмиты и потери пакетов в сети — единичный TCP-ретрансмит добавляет к запросу сотни миллисекунд практически незаметно для среднего, но заметно для конкретного пользователя.
Ни одна из этих причин не проявляется «в среднем» — все они по своей природе редкие и локальные события. Именно поэтому инструмент для их обнаружения тоже должен быть «редким и локальным» — то есть смотреть в хвост распределения, а не в его центр тяжести.
Что менять в мониторинге: смотреть на перцентили, а не на среднее
Из всего сказанного следует несколько практических выводов для настройки мониторинга производительности сервера:
- Алерты стройте на перцентилях, а не на среднем. Правило «алерт, если среднее время ответа выше X» пропускает именно те инциденты, которые сильнее всего бьют по пользователям — редкие, но тяжёлые всплески. Правило «алерт, если p95 выше X в течение 5 минут» реагирует на них напрямую.
- Держите на дашборде минимум три линии: p50, p95, p99. p50 показывает типичный опыт, p95 — что происходит с заметной частью пользователей, p99 — насколько плохо худшим случаям. Разрыв между p50 и p99 — сам по себе полезный индикатор: если он растёт, у сервиса накапливается проблема с хвостом задержек, даже если среднее и медиана выглядят стабильно.
- Формулируйте цели в терминах перцентилей (SLO), а не среднего. «95% запросов быстрее 300 мс» — измеримая и честная цель. «Среднее время ответа меньше 150 мс» такую цель на практике не гарантирует: под ней вполне может скрываться 5% запросов по несколько секунд.
- Логируйте медленные запросы отдельно. Если запрос попал за границу p99, полезно писать по нему детальный лог (или трейс) — именно такие случаи потом объясняют, откуда берётся хвост: конкретный SQL-запрос, конкретный эндпоинт, конкретное время суток.
- Проверяйте, какие метрики вообще стоит держать на виду каждый день — если в списке нет перцентилей отклика, это стоит исправить в первую очередь. Более широкий разбор набора базовых метрик есть в статье Какие метрики смотреть каждый день.
Отдельно стоит сказать про пороги алертов: недостаточно просто заменить «среднее» на «p95» в правиле — сам порог тоже нужно откалибровать по реальному поведению сервиса, а не поставить наугад. История о том, как неверно выбранный порог маскировал деградацию задолго до того, как она стала явной, хорошо показана в статье Порог алерта стоял на девяноста, а сервис умирал на семидесяти.
И ещё один нюанс, который легко упустить: перцентили — не панацея и не отменяют здравого смысла в интерпретации метрик в целом. Красивый ровный график сам по себе ничего не гарантирует, если под капотом мониторинг настроен неверно или измеряет не то, что нужно. Полезно держать в голове разбор типичных заблуждений о мониторинге — например, в статье Миф: «99,9% аптайм — это почти всегда» разобран похожий по духу случай, когда одно агрегированное число скрывает за собой совсем не радужную картину.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Почему бы просто не смотреть на максимум времени ответа вместо перцентилей?
Максимум — это единичное значение, которое может быть аномалией (например, сетевой сбой на секунду) и никак не отражает масштаб проблемы. p99 показывает границу для целого процента трафика, а не для одного случайного выброса, поэтому устойчивее к шуму и лучше подходит для алертов.
Какой перцентиль выбрать для SLO — p95 или p99?
Зависит от чувствительности сервиса. Для большинства веб-приложений разумный компромисс — p95 как основная цель и p99 как дополнительный индикатор здоровья хвоста. Для платёжных и других критичных операций часто имеет смысл жёстче следить именно за p99, потому что там любая задержка стоит дороже.
Можно ли считать перцентили без Prometheus, штатными средствами?
Да, многие веб-серверы и балансировщики умеют логировать время ответа каждого запроса, а дальше квантили можно посчитать хоть скриптом по логам — это медленнее и не в реальном времени, но для разовой диагностики вполне рабочий вариант, если под рукой ещё нет полноценного стека метрик.
Если p50 и p95 выглядят нормально, а жалобы всё равно есть — в чём смотреть дальше?
Стоит поднять окно до p99 и p99.9 — проблема может быть настолько редкой, что не попадает даже в 95-й процентиль, но при этом стабильно достаётся одним и тем же пользователям (например, из-за гео или конкретного сегмента данных). Здесь уже полезны детальные логи и трейсы отдельных медленных запросов, а не только агрегированные цифры.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →