Ограничение скорости Как прекратить получать ошибку 429 от каждой API-сервиса, с которым вы взаимодействуете
HTTP 429 слишком много запросов — как читать заголовки Retry-After, реализовывать экспоненциальный откат с дрожанием, понимать алгоритмы бакета токенов и бакета утечки, и реализовывать ограничение скорости на собственной API.
Вы получили код 429. Ваш скрипт активно обращается к API в течение последнего часа, ваши логи полны красных сообщений, и развертывание запланировано на 20 минут. В этот момент абстракция прекращается.
Код HTTP 429 «Слишком много запросов» означает, что вы отправили больше запросов, чем разрешено сервером в заданном временном интервале. Правильное чтение ответа и корректная попытка повтора — это навык, который большинство разработчиков приобретают только после того, как им пришлось столкнуться с этой проблемой. Вот полная картина.
То, что на самом деле сообщает 429
Код состояния — это только половина сообщения. Реальная информация содержится в заголовках:
Retry-After: 30— ждите 30 секунд перед повторной попыткой. Также может быть указан HTTP-дата:Retry-After: Mon, 08 Jun 2026 15:00:00 GMTX-RateLimit-Limit: 100— общее количество запросов, разрешённых в интервалеX-RateLimit-Remaining: 0— количество оставшихся запросов в текущем интервале (вы достигли нуля)X-RateLimit-Reset: 1749391200— Unix-временная метка, когда интервал обновляется
Не все API отправляют все эти данные. GitHub отправляет полный набор. Stripe отправляет Retry-After. Некоторые REST-API ничего не отправляют и ожидает, что вы догадаетесь. Если вы имеете Retry-After, используйте его точно — это то, что сервер сообщает о минимально безопасном времени ожидания. Если вы не используете это, то экспоненциальный откат становится вашим резервным решением.
Неправильный способ повторной попытки
Простая реализация выглядит так:
async function fetchWithoutBackoff(url) {
while (true) {
const res = await fetch(url);
if (res.ok) return res;
if (res.status === 429) continue; // immediately retry
}
}
Это активно вредит. Если 10 экземпляров вашей службы одновременно сталкиваются с кодом 429 и сразу начинают повторные попытки, все повторные попытки приходят в одно и то же время — проблема «гурманской стадии». Вы снова сталкиваетесь с ограничением скорости, немедленно, в бесконечном цикле, который может продолжаться и заставляет ваш клиент выглядеть как будто он намеренно злоупотребляет API.
Экспоненциальный откат с добавлением случайного сдвига
Правильный паттерн: каждая попытка повтора ждёт дольше предыдущей (экспоненциальный), а случайный сдвиг предотвращает синхронизацию повторных попыток между несколькими клиентами (сдвиг).
async function fetchWithBackoff(url, options = {}, maxRetries = 5) {
let attempt = 0;
while (attempt <= maxRetries) {
const res = await fetch(url, options);
if (res.ok) return res;
if (res.status !== 429) {
throw new Error(`Request failed: ${res.status}`);
}
if (attempt === maxRetries) {
throw new Error(`Rate limited after ${maxRetries} retries`);
}
// Use Retry-After if provided; otherwise exponential backoff + jitter
const retryAfter = res.headers.get('Retry-After');
let waitMs;
if (retryAfter) {
const seconds = isNaN(retryAfter)
? (new Date(retryAfter) - Date.now()) / 1000 // HTTP date
: Number(retryAfter); // seconds
waitMs = seconds * 1000;
} else {
const baseDelay = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s, 8s, 16s
const jitter = Math.random() * 1000; // 0–1000ms random offset
waitMs = baseDelay + jitter;
}
console.log(`Rate limited. Waiting ${Math.round(waitMs / 1000)}s (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, waitMs));
attempt++;
}
}
Линия сдвига — это часть, которую большинство реализаций пропускают. Без неё повторные попытки из нескольких параллельных процессов всё ещё приходят в кластерах. С её использованием они распределяются по временному интервалу ожидания.
Для API, которые возвращают Retry-After, используйте это значение как floor — если вы всё ещё получаете 429 после указанного ожидания, примените экспоненциальный откат на верхнем уровне.
Бакет токенов против бакета с утечкой
Два алгоритма доминируют в реализации ограничителей скорости. Понимание того, какой из них используется, даёт понимание того, как поведёт себя API при высокой нагрузке — и какой алгоритм следует использовать при создании собственного.
Бакет токенов
Бакет может содержать до N токенов. Каждый запрос стоит один токен. Токены пополняются по постоянному темпу (например, 10 в секунду). Если бакет пуст, запрос отклоняется или добавляется в очередь.
Поддерживает всплески. Если вы не делали запросов в течение длительного времени, вы накопили токены и можете отправить всплеск без нарушения лимита. API GitHub работает по этому принципу — 5 000 запросов в час, но вы можете использовать их одновременно, если не обращались к API в течение нескольких часов. Хорошо подходит для интерактивных сценариев с резкими пиками нагрузки.
Бакет с утечкой
Запросы попадают в очередь и постепенно вытекают с постоянной скоростью, независимо от того, насколько быстро они приходят. Если очередь заполнена, входящие запросы отбрасываются.
Плавный выход, без всплесков. Даже если у вас ещё есть квота, запросы поступают с установленной скоростью. Модуль Nginx limit_req использует этот подход. Лучше подходит для защиты нижестоящих систем от всплесков — полезно для доставки вебхуков, внешних API-вызовов и любых сценариев, где важна предсказуемая пропускная способность, а не выносливость к всплескам.
Как выбрать при реализации собственного: Пользовательские конечные точки, требующие всплесков — бакет токенов. Доставка вебхуков или вызовы внешних API — бакет с утечкой. Задачи в фоновом режиме, где важна плавная пропускная способность — бакет с утечкой.
Расчёт безопасных скоростей запросов
Перед написанием любой логики повторных попыток определите, сколько вы на самом деле можете делать. Если API говорит «1 000 запросов в час», это 16,67 запросов в минуту или 0,278 запроса в секунду. Добавьте буфер безопасности 20% и вы получите около 13 запросов в минуту — достаточно пространства для избежания проблем с временным перекрытием, когда два интервала пересекаются.
Используйте Калькулятор ограничений скорости для преобразования квот в скорости в секунду и минуту, определения правильного интервала ожидания между запросами и понимания того, как уровень параллельности влияет на риск всплесков.
Реализация ограничения скорости на собственном API
Если вы находитесь с другой стороны и хотите добавить правильное поведение 429 на своё собственное API:
- Выберите подходящую гранулярность. По IP-адресу легко, но не работает для сервисов, находящихся за NAT или с общим выходом. По API-ключу лучше, но требует аутентификации. По идентификатору пользователя — идеально, если у вас есть такой идентификатор. Не смешивайте гранулярности без понимания того, какой из них будет лучше.
- Всегда возвращайте
Retry-After. Код 429 безRetry-Afterвынуждает каждый клиент реализовывать собственный алгоритм отката. Вы получите больше проблем «гурманской стадии», а не меньше. - Используйте Redis для распределённого ограничения скорости. Счётчики в памяти не работают между несколькими экземплярами сервера. Redis
INCR+EXPIREявляется стандартным решением. Библиотеки, такие как rate-limiter-flexible (Node) и slowapi (Python/FastAPI) правильно абстрагируют этот механизм. - Логируйте каждый 429, который вы отправляете. Резкий рост 429-событий от одного ключа означает либо баг клиента, либо намеренное злоупотребление. Оба случая требуют своевременного знания.
- Не ограничивайте по ошибкам аутентификации. Возвращайте 401 при неверных учётных данных, а не 429. Ограничение по ошибкам аутентификации — это то, как вы случайно блокируете своих пользователей во время смены учётных данных.
Что делать прямо сейчас
Если вы сталкиваетесь с 429:
- Проверьте
Retry-Afterпервое — используйте его, если оно есть, не изобретайте собственную задержку - Реализуйте экспоненциальный откат с добавлением сдвига — код выше готов к копированию и вставке
- Запишите заголовок
X-RateLimit-Remainingв каждом ответе — вы можете быть неоправданно расходуете квоту - Кэшируйте ответы, когда данные не меняются часто
Если вы реализуете ограничение скорости: выбирайте библиотеку с поддержкой Redis, возвращайте Retry-After на каждый 429, мониторьте количество 429 по ключу и не ограничивайте по ошибкам аутентификации.
Код 429 не враг — это API, который сообщает вам ровно то, что пошло не так и (обычно) сколько нужно ждать. Большинство проблем с ограничением скорости возникают из-за игнорирования этого сообщения и немедленного повтора попыток. Не делайте этого.
Вам также может понравиться
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент было добавлено 15 Июня 2026
