Throughput и задержка: где p50 скрывает реальную картину
Разбираем throughput и задержка на примерах batch-задач и внутренних сервисов: почему p50 успокаивает, а очередь, p95 и RPS решают исход.

Почему p50 успокаивает слишком рано
p50 нравится всем по одной причине: он показывает середину. Половина запросов уложилась, например, в 300 мс, и график выглядит здоровым. Но прод держится не на середине, а на всем потоке.
Если каждая вторая операция быстрая, это еще не значит, что система справляется. Вторая половина может идти заметно дольше, а несколько совсем медленных запросов создают хвост. Именно хвост съедает пропускную способность, держит занятыми воркеры и растит очередь.
Простой пример: у сервиса p50 равен 250 мс, и на дашборде все спокойно. Но 5% запросов висят по 6-8 секунд из-за одного внешнего шага - медленного ответа модели, базы или проверки прав. Пока эти запросы не завершились, воркеры не берут новые задачи. В итоге throughput и задержка расходятся: медиана выглядит хорошо, а реальный объем обработанных задач в минуту падает.
Очередь делает картину еще хуже. Пользователь или соседний сервис ждут не только само выполнение, но и время до старта. p50 обычно слишком поздно показывает этот эффект. Он остается нормальным, пока быстрые запросы тянут медиану вниз. Команда смотрит на зеленый график и думает, что запас еще есть. Через час выясняется, что задачи уже копятся, SLA трещит, а разобрать хвост быстро не получается.
Один медленный участок часто тянет вниз весь поток. Не весь сервис целиком, а один шаг: обращение к провайдеру, запись в журнал, пересчет эмбеддингов, повторный запрос после таймаута. Если через этот шаг проходит каждый запрос, именно он задает темп системе. Быстрые участки уже не спасают.
Обычно ранний сигнал выглядит так: p50 почти не меняется, p95 и p99 растут, длина очереди и время ожидания ползут вверх, а число обработанных задач в минуту падает.
Поэтому медиана полезна только как один индикатор. Для batch-задач и внутренних сервисов она часто слишком добра к системе. Когда p50 наконец портится, проблема уже накопилась.
Где throughput важнее задержки
Если за час нужно обработать миллион событий, среднее время одного запроса мало утешает. Задержка отвечает на вопрос, как быстро проходит одна операция. Throughput отвечает на другой: сколько операций система реально переваривает за минуту.
Это особенно заметно там, где человек не сидит перед экраном и не ждет ответ. Ночная batch-задача по обогащению 100 тысяч карточек клиентов может спокойно терпеть 2-3 секунды на отдельный вызов, если весь поток укладывается в окно до утра. Если не укладывается, к началу рабочего дня очередь останется висеть, хотя p50 все еще будет выглядеть "нормально".
То же происходит в фоновой обработке документов, писем и звонков. Сервис разбора входящей почты, классификации PDF или суммаризации записей колл-центра живет не по одному запросу, а по общему объему за смену. Когда входящий поток выше, чем сервис успевает обработать, задержка сначала растет тихо, а потом очередь ломает весь график.
Типичные случаи, где это видно сразу, довольно приземленные: batch-процессы с жестким окном запуска, пайплайны, где следующий шаг ждет предыдущий, внутренние сервисы с волнами запросов после рассылки или начала смены, а также фоновые очереди с повторами, где старые задачи мешают новым.
У внутренних сервисов картина часто обманчива. Допустим, 500 сотрудников почти одновременно отправляют обращения на извлечение данных из договоров. Для одного человека лишние 700 мс не всегда заметны. Для системы это уже десятки минут суммарной работы, если параллелизм упирается в лимиты модели, GPU или провайдера.
В LLM-пайплайнах это видно еще сильнее. Один шаг чистит текст, другой делает классификацию, третий пишет краткое резюме. Если первый этап держит приемлемый p50, но пропускает мало запросов в секунду, остальные шаги простаивают или получают данные рывками. На графике средней задержки все выглядит терпимо, а нужный объем команда не успевает обработать.
Если нагрузка идет пакетами, очередями или волнами, сначала считайте пропускную способность. Задержку смотрите рядом с ней, а не вместо нее.
Пример с batch-задачей на 100 тысяч записей
Представьте ночной прогон: нужно обработать 100 тысяч карточек товара через LLM, чтобы получить теги и короткие описания до начала рабочего дня. У команды есть два варианта, и по p50 первый выглядит приятнее.
Вариант A дает p50 на уровне 320 мс, но система держит только 35 запросов в секунду без роста очереди. Вариант B отвечает с p50 около 700 мс, зато стабильно держит 160 запросов в секунду.
Если смотреть только на медиану, вариант A кажется быстрее. Один запрос действительно проходит раньше. Но batch-задача не оценивает отдельный удачный ответ. Она заканчивается только тогда, когда закрыт весь массив.
Считаем полный прогон. При 35 запросах в секунду вариант A обработает 100 тысяч записей примерно за 47 минут. Вариант B при 160 запросах в секунду закончит примерно за 10-11 минут. Разница огромная, хотя p50 у второго хуже больше чем в два раза.
Это особенно заметно, если ночное окно короткое. Допустим, у вас есть 30 минут до утренних отчетов. Вариант A не успеет даже при красивом p50. Вариант B успеет и оставит запас на повторы, запись результатов и мелкие сбои по пути.
Почему так выходит? Первый вариант быстро упирается в предел по воркерам, соединениям или токенам в минуту. Очередь растет, вторая половина запросов ждет дольше, p95 уезжает вверх. На дашборде p50 все еще аккуратный, но реальный прогресс batch идет медленно.
Второй вариант отвечает на один запрос чуть дольше, зато лучше переваривает поток под постоянной нагрузкой. Для batch-задач это часто лучший обмен. Вы теряете сотни миллисекунд на одну запись и выигрываете десятки минут на всем объеме.
Для ночных прогонов полезнее спрашивать не "какой запрос быстрее", а "кто раньше закроет все 100 тысяч записей". В таком сценарии побеждает не самый шустрый p50, а самая высокая пропускная способность без срыва под нагрузкой.
Пример с внутренним сервисом для сотрудников
Представьте сервис подсказок для 200 операторов контакт-центра. Он помогает быстро собрать ответ клиенту: оператор нажимает кнопку, отправляет короткий запрос и ждет 1-2 абзаца текста. Сами запросы маленькие. Проблема начинается не в их размере, а в синхронности.
Утром после старта смены десятки людей работают почти одновременно. Кто-то открывает одинаковый сценарий, кто-то отвечает на одну и ту же волну обращений, и запросы приходят почти в один момент. В спокойный час сервис отвечает за 2 секунды, и p50 выглядит здоровым.
Теперь добавим числа. Система успевает обрабатывать 30 запросов в секунду. В 9:00 прилетает всплеск на 80 запросов в секунду и держится 15 секунд. Очередь растет на 50 запросов в секунду, то есть за это время накапливается 750 запросов. Даже когда всплеск закончился, сервису нужно еще около 25 секунд, чтобы разобрать хвост.
Вот почему p50 и p95 дают разную картину. Первые запросы проходят быстро, часть сотрудников получает ответ почти сразу, и медиана остается приличной. Но у тех, кто попал в хвост очереди, ожидание легко уходит в 10, 15 или 25 секунд. Для оператора это уже заметная задержка: он теряет темп, переключается на ручной ответ или жмет кнопку еще раз.
Повторные нажатия делают только хуже. Если человек не увидел ответ через 6 секунд, он часто отправляет тот же запрос снова. Сервис кладет в очередь дубликат, потом еще один. Пропускная способность уходит не на полезную работу, а на копии одного и того же запроса.
В таком сценарии быстрая модель сама по себе не спасает. Если очередь растет быстрее, чем система успевает ее разбирать, пользователь все равно видит долгий ответ. Если компания пускает такие запросы через единый LLM API-шлюз, важно смотреть не только на время ответа модели, но и на длину очереди, лимиты провайдера и долю повторов.
Обычно хватает четырех метрик: запросы в секунду в пиковые 1-5 минут, p95 и p99, длина очереди и время ожидания до начала обработки, а также доля повторных нажатий и дубликатов.
Если p50 равен 2 секундам, а десятая часть операторов ждет по 20 секунд, сервис уже мешает работе. Для внутреннего инструмента это и есть реальная картина, а не красивая медиана.
Какие метрики смотреть вместе
p50 полезен как быстрый градусник. Он показывает, что происходит с серединой запросов, но молчит о хвостах, очередях и провалах под нагрузкой. Throughput и задержку нужно смотреть вместе, иначе выводы будут неверными.
Для LLM-сервиса одного числа почти никогда не хватает. Один и тот же RPS может означать совсем разную нагрузку: 100 коротких запросов на эмбеддинги и 100 длинных генераций с большим ответом грузят систему по-разному. Поэтому рядом с запросами в секунду стоит держать токены в секунду.
На одном экране полезно видеть пять вещей:
- RPS и токены в секунду. Первая метрика показывает поток запросов, вторая - сколько работы реально проходит через модели.
- p50, p95 и p99. p50 успокаивает, а p95 и p99 показывают, где пользователи и внутренние очереди уже чувствуют проблему.
- Длину очереди по минутам. Среднее значение скрывает всплески.
- Полное время завершения batch. Для задачи на 100 тысяч записей это важнее, чем удачный ответ за 800 мс.
- Ошибки, таймауты и ретраи. Без них цифры по задержке выглядят чище, чем есть на самом деле.
Небольшой пример быстро ставит все на место. Допустим, batch обрабатывает 50 тысяч документов. p50 держится на уровне 1 секунды, и команда довольна. Но очередь медленно растет, p99 подскакивает до 18 секунд, а 4% запросов уходят в ретраи. В итоге batch завершается не за 2 часа, как ждали, а за 3 часа 40 минут. Красивый p50 тут почти ничего не значит.
Для внутренних сервисов логика та же. Если сотрудники запускают массовую проверку договоров, им важнее, сколько задач система закроет за час, чем средняя задержка отдельного запроса. Хороший дашборд должен отвечать на простой вопрос: сервис успевает разгребать входящий поток или долг копится.
Если вы видите рост очереди при стабильном RPS, считайте это ранним сигналом. Обычно проблема уже не в одном медленном запросе, а в том, что сервис перестал переваривать общий объем работы.
Как посчитать нужный запас
p50 почти не помогает, когда сервис ловит пик. Запас считают не от "среднего" запроса, а от самой тяжелой минуты или самого плотного окна нагрузки. Сначала смотрят на поток запросов и токенов, и только потом - на аккуратную медиану.
Поднимите логи хотя бы за неделю и найдите реальный максимум. Обычно достаточно пяти чисел: запросы в минуту в обычный час и в пике, входные токены в минуту, выходные токены в минуту, долю ретраев и повторных вызовов, а также число одновременно висящих запросов.
Эти цифры быстро отрезвляют. Команда часто видит p50 в 1,2 секунды и думает, что все хорошо. А потом выясняется, что в пиковую минуту модель упирается не во время ответа, а в лимит токенов или параллельных запросов.
Дальше нужен запас. Для внутренних сервисов с ровной нагрузкой часто хватает 30-50% сверху. Для всплесков после рассылки, закрытия смены или запуска batch-задачи разумнее закладывать запас ближе к 2x. Если есть ретраи, считайте их частью нормальной нагрузки, а не редкой аварией. Они появляются именно тогда, когда системе и так тяжело.
После этого проверьте жесткие лимиты модели и провайдера: RPM, TPM, параллелизм, длину контекста. Это важнее, чем кажется. Модель может давать приятный p50 на коротких запросах и проваливаться на длинных ответах. Если вы идете через шлюз вроде RU LLM, стоит заранее проверить лимиты выбранного маршрута и запасной путь на случай, если провайдер начнет резать поток.
Тест нужен на живой смеси промптов. Не гоняйте только короткие шаблоны. Возьмите обычные запросы, длинные документы, случаи с большим ответом и часть ретраев. Иначе тест покажет слишком красивую картину.
Последняя проверка простая: смотрите, как очередь возвращается к нулю. Допустим, batch на 100 тысяч записей создает пик на 15 минут. Если после пика очередь исчезает за 5-10 минут, запас есть. Если она растет еще полчаса, система уже не держит нужную пропускную способность, даже если p50 все еще выглядит прилично.
Где команды ошибаются чаще всего
Чаще всего команды смотрят на p50 после короткого теста и успокаиваются. Прогнали сто запросов, получили среднюю картину и решили, что сервис готов. Но на малой выборке почти не видно хвостов: редких длинных ответов, скачков после прогрева, пауз на ретраи и упора в лимиты провайдера.
Из-за этого throughput и задержка выглядят лучше, чем в реальной работе. В batch-задаче на 100 тысяч записей даже лишние 2-3% медленных или повторных запросов превращаются в часы сверху. Для внутреннего сервиса это означает другое: p50 красивый, а сотрудники все равно ждут ответ в самый загруженный час.
Другая частая ошибка - сводить в одну цифру короткие и длинные запросы. Один промпт на классификацию заявки и один запрос на разбор длинного документа живут в разном мире. Если смешать их в общем p50, метрика станет почти бесполезной. Она скроет, какой тип трафика забивает очередь и где именно падает пропускная способность.
Обычно проблемы всплывают в четырех местах:
- тестируют на маленьком объеме и без длительной нагрузки;
- считают только успешные ответы и забывают про ретраи;
- пускают batch и срочные запросы в один пул;
- сравнивают варианты по цене за запрос, а не по цене готового результата.
С ретраями ошибка особенно дорогая. Если провайдер держит rate limit, система начинает повторять часть вызовов. Формально запросов стало больше, а полезной работы - нет. На бумаге задержка может выглядеть терпимо, но фактическая скорость обработки очереди падает.
Смешивание batch и срочного трафика в одном пуле бьет по обеим сторонам. Ночной прогон отчетов легко съедает весь запас, и утром внутренний помощник для сотрудников отвечает рывками. Если вы используете единый OpenAI-совместимый endpoint, очереди и лимиты лучше разделять сразу, а не после первого сбоя.
С ценой тоже часто промахиваются. Модель с низкой ценой за токен может проиграть, если отвечает дольше, чаще уходит в повторы или дает меньше готовых результатов за час. Считать стоит не "сколько стоит запрос", а "сколько стоит обработать 10 тысяч записей" или "сколько стоит один полезный ответ сотруднику".
Быстрые проверки перед запуском
Если у вас нет цели по времени для полного прогона, вы не знаете, работает система нормально или просто "не совсем сломалась". Для batch-задачи цель должна быть простой: сколько записей нужно обработать и к какому часу все должно закончиться. Например, ночной прогон на 100 тысяч строк должен укладываться в 45 минут, а не "как получится".
Средняя задержка здесь почти бесполезна. Она может выглядеть приятно даже в тот момент, когда очередь уже растет, а хвостовые запросы висят слишком долго. Поэтому throughput и задержку нужно смотреть вместе, а p50 держать на втором плане.
Перед релизом обычно хватает пяти проверок:
- Зафиксируйте время на полный batch или на обработку очереди за смену.
- Постройте графики p95, p99 и длины очереди.
- Отделите срочные запросы от фоновых.
- Прогоните два теста: короткий пик и длинную нагрузку.
- Заранее решите, что делать при rate limit.
Отдельный маршрут для срочных запросов часто спасает внутренние сервисы. Допустим, ночью идет массовая классификация документов, а утром служба поддержки открывает карточки клиентов. Если оба потока идут в один пул, batch легко забирает весь ресурс.
Для команд, которые ходят к нескольким моделям через единый шлюз, это еще важнее. Rate limit у одного провайдера не должен останавливать весь поток. Лучше заранее решить, какие запросы можно отложить, какие переключать на другую модель, а какие нельзя задерживать вообще.
Если после часового теста очередь не растет, p95 держится в разумных пределах, а batch укладывается в целевое окно, это уже хороший признак. Если p50 красивый, но хвост длинный и очередь живет своей жизнью, запуск лучше не торопить.
Что делать дальше в проде
В проде p50 быстро теряет смысл, если система живет в смешанном режиме. Один и тот же пул моделей не должен одновременно тянуть чат для сотрудников и ночную обработку больших пакетов. Иначе batch съест доступную пропускную способность, а интерактивные запросы начнут прыгать по задержке даже при нормальном среднем значении.
Разведите трафик по разным маршрутам
Интерактивные запросы лучше вести по отдельному маршруту с жестким лимитом очереди и понятным тайм-аутом. Batch-задачи отправляйте в другой маршрут, где важнее не мгновенный ответ, а стабильный throughput на длинной дистанции.
На практике достаточно нескольких правил: дайте интерактивному трафику отдельный пул concurrency, ограничьте batch по скорости отправки, задайте разные ретраи для чата и фоновой обработки, а модель или провайдера переключайте отдельно для каждого типа задачи.
Смотрите на очередь, а не только на latency
Метрики собирайте не общей кучей, а по трем срезам: модель, провайдер, тип задачи. Если одна модель держит p50 в 1,2 секунды, но опустошает очередь вдвое медленнее соседней, это плохой выбор для batch. Для внутренних сервисов картина может быть обратной: пусть throughput ниже, если пользователь получает ровный ответ без резких пиков на p95.
Полезная метрика для прода - время опустошения очереди. Она отвечает на простой вопрос: если новые задачи перестанут приходить прямо сейчас, за сколько минут система разберет хвост. Допустим, в очереди 12 000 задач, а текущий маршрут стабильно вывозит 100 задач в минуту. Значит, хвост уйдет только через 2 часа. p50 тут может выглядеть прилично, но бизнес уже опаздывает.
Если вам нужен единый OpenAI-совместимый endpoint в РФ, имеет смысл смотреть на то, как шлюз маршрутизирует трафик, показывает метрики по провайдерам и помогает разделять рабочие потоки. У RU LLM это можно проверить без смены SDK и промптов: меняется base_url, а дальше уже проще сравнить маршруты и понять, где система держит нагрузку лучше.
Хороший следующий шаг довольно приземленный: разделите трафик, добавьте метрику времени опустошения очереди и посмотрите графики не за час, а за полную рабочую смену. После этого обычно сразу видно, где у вас быстрый сервис, а где просто красивая медиана.
Часто задаваемые вопросы
Почему p50 часто вводит в заблуждение?
Потому что p50 показывает только середину потока. Он скрывает медленные 5–10% запросов, а именно они держат занятыми воркеры, растят очередь и снижают реальный объем обработки.
Когда throughput важнее задержки?
Когда задача живет по общему объему, а не по одному ответу. Batch, фоновые очереди, разбор документов и массовые внутренние запросы выигрывают от высокой пропускной способности, даже если один запрос идет чуть дольше.
Какие метрики смотреть рядом с p50?
Смотрите на p95 и p99, длину очереди, время ожидания до старта обработки, RPS и токены в секунду. Для batch еще важнее время полного прогона, потому что именно оно отвечает на вопрос, успеет ли система закончить работу вовремя.
Как понять, что сервис уже захлебывается в очереди?
Признак простой: очередь растет, а число завершенных задач в минуту не успевает за входящим потоком. В этот момент p50 часто остается приличным, но пользователи уже ждут дольше, а хвост только увеличивается.
Что важнее для batch на 100 тысяч записей?
Считайте не скорость одного запроса, а время закрытия всего массива. Если вариант с p50 700 мс обрабатывает поток в четыре раза быстрее, он закончит batch раньше и оставит запас на повторы и сбои.
Почему сотрудники жалуются, хотя p50 выглядит нормально?
Потому что часть сотрудников попадает в хвост очереди, а медиана этого не показывает. Если в пик приходит больше запросов, чем сервис успевает разобрать, кто-то получает ответ быстро, а кто-то ждет 15–20 секунд и жмет кнопку снова.
Как посчитать запас под пик?
Берите не средний час, а самую тяжелую минуту или плотное окно нагрузки. Для ровного трафика часто хватает запаса 30–50%, а для всплесков и ретраев лучше закладывать около 2x и отдельно проверять лимиты по токенам, RPM и параллелизму.
Как правильно тестировать LLM-сервис перед релизом?
Не ограничивайтесь коротким тестом на сотне запросов. Прогоните короткий пик и длинную нагрузку на живой смеси промптов, добавьте ретраи и проверьте, как быстро очередь возвращается к нулю после всплеска.
Нужно ли разделять batch и срочный трафик?
Да, иначе batch легко съест весь доступный ресурс и интерактивные запросы начнут тормозить. Дайте срочному трафику свой пул и свои тайм-ауты, а фоновым задачам — отдельный маршрут с ограничением скорости.
Как сравнивать модели и провайдеров по деньгам?
Сравнивайте не цену одного запроса, а цену готового результата. Дешевая модель легко проигрывает, если она чаще уходит в ретраи, медленнее разбирает очередь или не успевает закрыть нужный объем за час.