Слепота наблюдения

Вы уже научились отслеживать среднюю скорость запросов на проекте, и это большой шаг. Без преувеличений и какой либо иронии.
И теперь, когда вы перешли от "не измеряем ничего" до "измеряем среднее" — вы попали в ловушку.

Пока вы с удовольствием наблюдаете в отчетах красивые 200ms — ваши пользователи стучат в службу поддержки со словами "у меня все висит".
И они не врут, у них действительно время ответа порядка 6 секунд. Но и вы не врете, у вас действительно 200ms в отчете!

Врет метрика, а вы ей верите.

Давайте разбираться.

Ваши пользователи не живут в мире среднего. У них есть один единственный запрос, и они определяют по нему, быстрый у вас сервис или не особо.
1000 пользователей за минуту отправила по одному запросу. Распределение на графике.

Получаем среднюю задержку 272ms в окне за минуту на дашборде и 52 недовольных пользователей из которых 11 даже слегка в шоке от того что в 2025 кто то может отвечать в районе 10 секунд.

А теперь давайте добавим сюда перцентили.
Перцентиль — это значение, ниже которого укладывается определённый процент всех наблюдений. Обозначается как pX, где Х это процент порога.
Например, p95 — это время, меньше которого укладываются 95% всех запросов, а оставшиеся 5 % — самые медленные.

Для нашего примера p50 — 103ms, p95 — 1319ms, p99 — 6169ms
Вот теперь мы видим откуда приходят жалобы в службу поддержки.
Правда непонятно откуда такие задержки, по 10 секунд.

Придется взглянуть на физику распределенных систем чуть ближе...


Закон Литтла (Little’s Law) и чуть чуть теории массового обслуживания (queueing theory).

Чтобы понять откуда взялись задержки в десятки раз больше среднего нужно посмотреть на систему как на очередь.

Закон Литтла

L = λ × W

Где:
L — среднее число запросов в системе (или очереди)
λ — скорость прихода запросов (RPS)
W — среднее время в системе (задержка)

Всего 3 параметра, но большинство инженеров, если и знают, то не используют на практике.

Сколько запросов обрабатывается одновременно?
Дано: λ = 100 req/s, W = 200ms
L = 100 × 0.2 = 20 запросов в системе одновременно
Теперь вы можете посчитать сколько к примеру на сервере будет съедено памяти и сколько используется коннектов к БД. Если 1 запрос потребляет 20Mb — для нашего примера общее потребление будет 400Mb.

Какая задержка при текущей нагрузке?
Дано: λ = 50 req/s, в среднем L = 5 активных запросов.
W = L/λ = 5/50 = 0.1s = 100ms — среднее время жизни запроса в системе.
Это еще не SLA, сначала доберемся до μ и ρ.

Какая нагрузка сейчас у системы?
Дано: W = 300ms, L \approx N = 10 воркеров
λ = L/W = 10/0.3 = 33.3 req/s текущая нагрузка при полной занятости. Не предел, максимум зависит от μ (скорости обслуживания одного воркера).

Но сам по себе закон Литтла не учитывает загрузку системы, для этого нам придется обратиться к теории очередей чтобы разобраться как утилизация влияет. Если нужны подробности — вам под кат, если результаты — переходите сразу к графику.

Подробные подробности теории очередей и утилизации

Теория массового обслуживания или теория очередей (Queueing Theory) — это раздел математики, который изучает, как ведут себя системы с заявками и ограниченными ресурсами. (Вариант определения из Википедии технически более корректный, но очень уж сложно понять)

  • λ (лямбда) — скорость поступления запросов. Например 120rps

  • μ (мю) — максимальная скорость обслуживания одного воркера. Например 1 воркер обслуживает 20rps.

  • ρ (ро) — утилизация системы: ρ = λ / (N×μ). При ρ = 0.8 воркеры заняты на 80%

  • N — число параллельных обслуживающих каналов. Воркеры, потоки, коннекты, консьюмеры.

  • S — среднее время обслуживания одного запроса. S = 1/μ. при μ=20 S = 1/20 = 50ms

  • W — уже встречались в законе Литтла, среднее время, которое запрос проводит в системе (ожидание + выполнение)

  • L — тоже встречали в законе Литтла, среднее число запросов в системе

Базовая модель M/M/1(поток запросов случайный, время обслуживания случайное, один воркер/сервер)

Для нее справедливо W = \frac{1}{μ - λ}, \quad L = \frac{λ}{μ - λ}

Утилизация ρ = \frac{λ}{N × μ}. При N=1 будет ρ = \frac{λ}{μ}

ρ показывает, какая доля времени система занята. При 0.5 — сервер работате в пол силы, при ρ → 1  очередь растет быстрее чем сервер успевает обрабатывать.

Влияние на задержку можно посчитать по формуле W = \frac{S}{1 - ρ}

Влияние на среднее число запросов в системе по формуле L = \frac{ρ}{1 - ρ}

Слева время, справа количество. При 90% загрузке система в 10 раз медленнее и держит в 9 раз больше запросов.
Слева время, справа количество. При 90% загрузке система в 10 раз медленнее и держит в 9 раз больше запросов.

Если коротко

  • При повышении утилизации базовая задержка растет по экспоненте. При ρ=0.5— вырастает в 2 раза, при ρ=0.9— в 10 раз.

  • При повышении утилизации количество запросов растет так же по экспоненте.

  • В реальности чаще встречается модель G/G/N — несколько воркеров и произвольные распределения времени обслуживания.
    Хвосты там ещё тяжелее, поэтому эти формулы — эвристика, а целевую утилизацию стоит держать ниже.

Что это значит для нас?
Малое ρ (до 0.7) — систему устойчива, задержки предсказуемы.
Среднее ρ (0.8-0.9) — начинаются хвосты. p95/p99 растут.
Большое ρ (>0.95) — очередь растет лавинообразно. Время отклика предсказать невозможно.

Оценка конкурентности под бюджет.
Теперь, зная как ρ влияет на хвост, можем прикинуть нужную конкурентность.
L^* ≈ λ_{\text{target}} × p95_{\text{target}}

Пример:
Цель — выдерживать 200rps при p95 = 150ms;
L^* = 200 × 0.15 = 30
Это консервативная оценка: закладывайте буфер под хвост (p95 > среднее). Держим ρ < 0.7, чтобы хвост не взрывался.
Точное значение можно получить только через нагрузочное тестирование, но формула даст стартовую точку для планирования конкурентности.
А если внутри есть fan-out — нагрузка умножается.


Бюджет задержек

Если вы уже перешли от замеров среднего до перцентилей — возможно пора сделать еще одни небольшой но важный шаг. Шаг к e2e бюджету.

Давайте представим что у нас есть CUJ e2e SLO, p95=300ms. (critical user journey end to end service level objective. Набор конечно вышел, аж самому страшно)
Но как его достичь? Нельзя просто сказать "все сервис должны быть быстрыми а очереди пустыми". Без декомпозиции каждый сервис выставит свой таймаут наугад и e2e рассыпется.

Давайте посмотрим что такое вообще e2e бюджет.

T_{e2e} = \sum_i T_i + \text{RTT} + \text{буфер}

  • T_i — задержка конкретного узла (гейт, оркестратор, сервис, хранилище, внешние зависимости и т.д.),

  • RTT — сеть/интернет,

  • буфер — запас под вариации и хвосты.

Теперь будем декомпозировать.

И первым в голову приходит мой любимый антипаттерн "всем по справедливости". Когда у вас 5 сервисов и вы каждому раздаете по 60ms не учитывая фанаут и критичность.

Второй — "бюджетирования в ноль", когда забывают оставить буфер. Гейту дадим 60ms, оркестратору 80, зависимостям оставим 120, сеть съест 40. А потом p95 всплеск на любом этапе и в бюджет не уложились. А всплеск будет. И вероятность у него не 5%, потому что мы говорим про последовательные вызовы. Для 4 узлов 1 - 0.95^4 \approx 18.45\%\,. для 6 1-0.95^6 \approx 26.5\%, для 10 — 40%. Добавляем сюда корреляции (общие пики CPU/GC/сеть) и получаем еще более печальную картину.

Вобщем без буфера никуда. Закладывайте от 15% до 25% на весь e2e бюджет.
Гейт 30, оркестратор 40, зависимости 120, сеть 60, общи буфер 50.

И теперь команда отвечающая за гейт знает что хэпипас должен укладываться в 30ms, в противном случае они факапят общий e2e. А вы знаете кто ответственен за срыв SLO и куда прикладывать усилия по оптимизации.


Что дальше?

Теперь у вас есть:

  • Понимание почему среднее врёт (перцентили!)

  • Инструменты оценки (закон Литтла, утилизация)

  • Метод планирования (e2e бюджет с буфером)

Следующий шаг — научить систему соблюдать бюджет в рантайме а не только в документации. Дедлайны, отмена и десяток паттернов для борьбы с хвостовой задержкой. Но это уже тема отдельной статьи.

А пока — пересчитайте вашу конкурентность по формуле L* ≈ λ·p95, проверьте утилизацию (держите ρ < 0.7) и разложите ваш SLO на критический путь с 20% буфером.

Хватит на месяц работы, а там глядишь и про управление хвостами напишу.

Удачи!

Комментарии (2)


  1. Camundaslav
    27.10.2025 10:39

    Насчет высоких перцентилей. В книге "Запускаем Prometheys" от O'Reilly этот случай тоже разбирался.

    Там посыл был такой, что превышения высоких перцентилей(99.9+%) часто генерируют VIP клиенты, которые как раз интересны бизнесу в первую очередь. У них может быть большая история заказов, всякие сложные сохраненные фильтры товаров и т.д. А это все как раз и приводит к медленным ответам.

    Вывод такой, что нужно обязательно смотреть высокие перцентили и в идеале собирать как можно больше метрик, чтобы было проще локализовать медленные запросы


    1. T2ig Автор
      27.10.2025 10:39

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

      это не отменяет справедливости ваших слов, просто нужно без фанатизма относиться к коллекционированию метрик