LLM API и интеграция
Rate limit в API нейросетей: что делать при отказе
Отказ по частоте запросов — самая недооценённая ошибка в работе с API моделей. Её обычно чинят самым вредным способом: повтором сразу же, без паузы. Это ровно то действие, которое усугубляет ситуацию, потому что сервис уже перегружен, а вы добавляете нагрузки. Между тем лимиты — не поломка и не признак плохого провайдера: это нормальный механизм, без которого один клиент со всплеском трафика ухудшал бы работу всем остальным. Разбираем, откуда берутся ограничения, чем повтор отличается от очереди, и почему правильная архитектура делает лимиты почти незаметными.
Откуда берутся ограничения
Причина физическая, и понимание её помогает выбрать реакцию.
Мощности под инференс конечны и дороги. Видеокарта обрабатывает ограниченное число запросов одновременно, и это число не растягивается по желанию. Когда обращений больше, чем ёмкость, варианта ровно два: ставить всех в очередь, ухудшая время ответа для всех, либо отказывать части, сохраняя качество обслуживания остальным.
Лимит — это выбор второго варианта с понятными правилами. Он делает поведение системы предсказуемым: вы знаете границу заранее, вместо того чтобы обнаруживать её по растущим задержкам.
Ограничивают обычно по нескольким осям одновременно: число запросов в единицу времени, число токенов в единицу времени, число одновременных обращений. Упереться можно в любую, и по коду ошибки не всегда видно, в какую именно.
Почему повтор сразу же — худшее решение
Разберём, что происходит при наивной обработке.
Запрос отклонён. Код немедленно отправляет его снова. Сервис по-прежнему перегружен, отклоняет снова. Код повторяет.
Получается цикл, в котором вы генерируете нагрузку с максимальной скоростью, на которую способны, — и делаете это ровно в момент, когда система и так не справляется. Хуже того, так же поступают все остальные клиенты, и перегрузка продлевается коллективными усилиями.
Правильная реакция — увеличивающаяся пауза. Первая попытка через секунду, вторая через две, третья через четыре. Смысл в том, чтобы дать системе разгрузиться, а не соревноваться с другими за место.
Второе обязательное условие — предел числа попыток. Три обычно покрывают подавляющее большинство временных сбоев. Десять не покрывают ничего сверх этого, зато превращают короткий всплеск в долгую деградацию вашего же сервиса: пользователь ждёт минуту вместо честного сообщения об ошибке.
Третье, что стоит добавить, — небольшой случайный разброс в паузе. Без него все ваши процессы, отказавшие одновременно, одновременно же и повторят, воспроизведя всплеск.
Очередь вместо повторов
Здесь начинается архитектурная часть, и она решает проблему принципиально, а не смягчает.
Повтор — это попытка протолкнуть запрос сейчас. Очередь — признание, что сейчас не получится, и перенос на потом.
Что даёт очередь:
Всплеск сглаживается. Пришло тысяча запросов за минуту, обрабатываете вы двести — остальные восемьсот не теряются и не долбятся в стену, а ждут.
Скорость становится вашим параметром. Вы сами задаёте, с какой частотой доставать из очереди, и держитесь ниже границы вместо того, чтобы упираться в неё.
Отказ перестаёт быть событием. Если отказ всё-таки произошёл, запрос возвращается в очередь, а не теряется.
Разделение простое: интерактивные запросы — напрямую, фоновые — через очередь. Пользователь, ждущий ответа на экране, не переживёт очереди. Ночная обработка накопленных документов не переживёт отказов.
Кстати, именно из-за этого разделения появляются тарифы, зависящие от времени: провайдеры пытаются экономически подтолкнуть фоновую нагрузку в непиковые часы. Мы разбирали такой случай в материале про тариф по часам суток.
Как понять, во что именно вы упёрлись
Диагностика, без которой лечение выбирается наугад.
Ограничения обычно устроены по нескольким осям сразу, и симптом у них один — отказ. Но причины разные, и меры тоже.
Упёрлись в число запросов. Много коротких обращений подряд. Лечится ограничителем скорости и объединением мелких запросов там, где это уместно.
Упёрлись в число токенов. Запросов немного, но каждый тащит огромный контекст. Лечится обрезкой истории и уменьшением того, что вы отправляете, — а заодно и удешевляет работу.
Упёрлись в одновременность. Запросов в секунду немного, но все они длинные и висят одновременно. Лечится ограничением параллелизма, а не паузами между запусками.
Отличить их друг от друга помогает простое наблюдение: посмотрите, что у вас в момент отказа было велико — количество, объём или число незавершённых обращений. Обычно ответ очевиден с первого взгляда на график, если такой график вообще есть.
Отсюда и минимальный набор того, что стоит измерять: запросов в минуту, токенов в минуту, одновременных обращений в пике. Три числа, которые показывают приближение к границе до того, как вы в неё упрётесь.
Почему всплески опаснее среднего
Наблюдение, которое объясняет, почему «у нас небольшая нагрузка» и «мы упираемся в лимит» прекрасно сосуществуют.
Средняя нагрузка почти ничего не говорит. Тысяча запросов за час — это меньше одного в секунду в среднем, и звучит безобидно. Но если они приходят пачкой за две минуты, мгновенная скорость в тридцать раз выше средней.
Такие всплески возникают сами собой: по расписанию запустилась обработка, пользователи пришли после рассылки, повторы после сбоя наложились друг на друга. Последний случай особенно неприятен, потому что сбой порождает всплеск, который порождает новые сбои.
Отсюда практическое: измерять надо пик, а не среднее. И закладывать ограничитель скорости даже там, где по средним цифрам он не нужен, — именно он и превращает пачку в ровный поток.
Как не упираться в лимит
Пять приёмов, отсортированных по отношению эффекта к трудозатратам.
- Считайте свою скорость. Большинство упирается в лимит не из-за нагрузки, а из-за отсутствия ограничителя на своей стороне: цикл по списку отправляет запросы так быстро, как может.
- Распараллеливайте с ограничением. Не «в один поток» и не «все сразу», а фиксированное число одновременных обращений. Это одна из самых частых недоработок в интеграциях.
- Уберите лишние запросы. Часть нагрузки — это повторные обращения за тем, что уже спрашивали. Кэш результатов на своей стороне снимает её без всякой борьбы с лимитами.
- Разведите нагрузку по ключам. Отдельный ключ под каждый сценарий даёт не только учёт расхода, но и изоляцию: фоновая задача не съест лимит интерактивного сценария.
- Не держите один запрос ради экономии. Соблазн упаковать двадцать задач в один огромный запрос понятен, но такой запрос дороже по контексту и дольше по времени.
Третий пункт стоит развернуть: доля повторяющихся запросов в реальных сервисах обычно выше, чем кажется. Прежде чем повышать лимиты, полезно посмотреть, сколько обращений можно было бы не делать вовсе.
Что с этим у нас
Проговорим для ясности.
Отказ по частоте приходит кодом rate_limit_exceeded. Он относится к тому классу отказов, при которых повтор осмыслен — в отличие от некорректного запроса или закрытого маршрута, где повторять бессмысленно, пока вы не измените код. Полный разбор всех кодов — в материале про отказы маршрута.
Деньги за отказ, случившийся до обращения к сервису модели, не списываются. Но если модель уже начала работу, за проделанную часть счёт возможен — ещё один довод не устраивать бесконечные повторы дорогих запросов.
Отдельный ключ под каждый сценарий заводится за минуту и решает сразу две задачи: видно, кто сколько тратит, и один сценарий не мешает другому.
Чего мы не утверждаем
Не приводим конкретных числовых лимитов. Они зависят от условий и меняются; актуальные значения нужно смотреть в своём кабинете и в документации, а не в статье.
Не обещаем, что очередь решит всё. Она сглаживает всплески, но не увеличивает пропускную способность. Если средняя нагрузка выше ёмкости, очередь просто растёт.
Не описываем поведение других провайдеров. У каждого свои оси ограничений и своя реакция на превышение.
Что держать в голове
Лимит — это механизм честного распределения конечной ёмкости, а не поломка. Реакция на него определяется тем, интерактивный запрос или фоновый: первый повторяют с растущей паузой и жёстким пределом, второй ставят в очередь.
Самый действенный шаг обычно не в обработке отказов, а до неё: ограничитель скорости на своей стороне и кэш повторяющихся обращений убирают большую часть упоров в лимит вместе с причиной.
Как мы проверяем факты и почему у каждого стоит источник — на странице о проекте. Отдельный ключ с собственным лимитом под каждый сценарий: keydealer.ru/login.
Частые вопросы
Что означает ошибка rate_limit_exceeded?
Что вы обращаетесь чаще, чем позволяет действующее ограничение. Это единственный класс отказов, при котором повтор осмыслен — но с паузой, а не сразу.
Зачем вообще нужны лимиты?
Мощности под инференс конечны. Без ограничения один клиент со всплеском нагрузки испортил бы работу всем остальным.
Как правильно повторять запрос?
С увеличивающейся паузой и жёстким пределом числа попыток. Повтор сразу же усугубляет перегрузку, а бесконечные попытки превращают короткий сбой в долгую деградацию.
Чем очередь лучше повторов?
Очередь сглаживает всплеск: запросы не теряются, а выполняются с задержкой. Повторы же создают дополнительную нагрузку ровно в тот момент, когда её и так много.
Списываются ли деньги за отказ по лимиту?
Нет, если отказ произошёл до обращения к сервису модели.