В любой интеграции рано или поздно появляется одна и та же проблема. Вы отправляете запрос к API — и он не проходит. Иногда сервер отвечает ошибкой, иногда сеть даёт сбой, иногда API просто говорит: «слишком много запросов».
В логах это выглядит примерно так:
429 Too Many Requests
или
500 Internal Server Error
Если система не умеет правильно реагировать на такие ситуации, интеграция становится нестабильной:
- часть запросов падает
- данные не синхронизируются
- события теряются
- система начинает бесконечно повторять запросы
Чтобы этого не происходило, в любой серьёзной интеграции используют два базовых механизма:
rate limit и retry.
Первый отвечает за контроль скорости запросов, второй — за повторные попытки при ошибках.
Если их правильно реализовать, интеграция становится устойчивой даже при сетевых сбоях и высокой нагрузке.
Что такое rate limit
Rate limit — это ограничение на количество запросов, которые можно отправить к API за определённый промежуток времени.
Почти все публичные API используют такие ограничения.
Примеры:
- GitHub API — 5000 запросов в час
- Stripe — примерно 100 запросов в секунду
- многие SaaS — 60 запросов в минуту
Когда лимит превышен, сервер возвращает ответ:
429 Too Many Requests
Иногда вместе с дополнительным заголовком:
Retry-After: 10
Это означает, что новый запрос можно отправить только через 10 секунд.
Такие ограничения нужны для защиты инфраструктуры и справедливого распределения ресурсов между клиентами.
Почему API ограничивают запросы
На первый взгляд может показаться, что rate limit — это просто неудобство для разработчиков. Но у этого механизма есть важные причины.
Первая причина — защита сервера. Если один клиент отправит тысячи запросов в секунду, это может перегрузить систему.
Вторая причина — равномерное использование ресурсов. Ограничения гарантируют, что один пользователь не займёт всю мощность сервиса.
Третья причина — безопасность. Rate limit помогает защищаться от brute force и других атак.
Что происходит без контроля rate limit
Представим простую интеграцию, которая отправляет данные в API.
Код может выглядеть так:
for (const event of events) {
await api.send(event)
}
Если событий немного — всё работает нормально. Но если их становится тысячи, система начинает отправлять огромное количество запросов.
В какой-то момент API начинает отвечать ошибкой:
429 Too Many Requests
Если код не умеет корректно обрабатывать такие ответы, интеграция начинает работать всё хуже:
- запросы падают
- система перегружает API
- часть данных теряется
Поэтому важно контролировать скорость отправки запросов.
Что такое retry
Retry — это повторная попытка выполнить запрос после ошибки.
Смысл retry в том, что многие ошибки являются временными.
Например:
- сервер был перегружен
- балансировщик вернул ошибку
- сеть на секунду оборвалась
В таких случаях повторный запрос часто проходит успешно.
Типичные ошибки, при которых используется retry:
- 500 Internal Server Error
- 502 Bad Gateway
- 503 Service Unavailable
- timeout
Во всех этих ситуациях повторная попытка имеет смысл.
Когда retry делать не нужно
Некоторые ошибки означают, что запрос никогда не станет успешным, пока не изменятся данные.
Например:
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
Если повторять такие запросы, система будет просто создавать лишнюю нагрузку.
Поэтому retry должен применяться только к временным ошибкам.
Проблема наивного retry
Самая простая реализация retry выглядит так:
try {
await api.request()
} catch (e) {
await api.request()
}
Но такой подход может привести к серьёзной проблеме.
Если сервер уже перегружен, мгновенные повторные запросы только увеличат нагрузку. В результате система может попасть в состояние, когда тысячи клиентов одновременно начинают повторять запросы.
Это называют retry storm — шторм повторных запросов.
Чтобы этого избежать, используют более аккуратный алгоритм.
Exponential backoff
Один из самых популярных алгоритмов retry — exponential backoff.
Его идея очень проста: каждая следующая попытка выполняется с увеличивающейся задержкой.
Например:
1 попытка — сразу 2 попытка — через 1 секунду 3 попытка — через 2 секунды 4 попытка — через 4 секунды 5 попытка — через 8 секунд
Это даёт серверу время восстановиться и резко снижает нагрузку.
Пример retry на JavaScript
Простейшая реализация retry с exponential backoff:
async function requestWithRetry(fn, retries = 5) {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await fn()
} catch (error) {
if (attempt === retries - 1) {
throw error
}
const delay = 2 ** attempt * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
Использование:
await requestWithRetry(() => fetch("https://api.example.com"))
Если запрос завершится ошибкой, функция автоматически попробует снова.
Ограничение скорости запросов
Помимо retry, часто нужно контролировать скорость отправки запросов.
Самая простая схема — использовать очередь.
Сначала задачи попадают в очередь, затем worker отправляет их в API с контролируемой скоростью.
Простейший limiter может выглядеть так:
class RateLimiter {
constructor(limit, interval) {
this.limit = limit
this.interval = interval
this.queue = []
this.active = 0
}
async schedule(task) {
return new Promise(resolve => {
this.queue.push({ task, resolve })
this.run()
})
}
run() {
if (this.active >= this.limit || this.queue.length === 0) return
const { task, resolve } = this.queue.shift()
this.active++
task().then(result => {
resolve(result)
setTimeout(() => {
this.active--
this.run()
}, this.interval)
})
}
}
Такой limiter позволяет отправлять, например, не больше 5 запросов в секунду.
Как это выглядит в реальной архитектуре
В production системах схема обычно выглядит так:
- события попадают в очередь
- worker берёт задачу
- rate limiter контролирует скорость
- отправляется API-запрос
- при ошибке включается retry
Такая архитектура позволяет системе:
- не превышать лимиты API
- корректно обрабатывать временные ошибки
- не терять данные при сетевых сбоях.
Итог
Rate limit и retry — это фундамент любой надёжной интеграции.
Rate limit контролирует скорость запросов и защищает API от перегрузки. Retry помогает системе автоматически восстанавливаться после временных ошибок.
Даже простая реализация этих механизмов значительно повышает устойчивость интеграций и предотвращает потерю данных при работе с внешними сервисами.