Представьте: ваше приложение работает идеально, пока внезапно не начинает сыпать ошибками. Вы лезете в логи и видите бесконечный поток кодов 429 и 503. Самая большая ошибка здесь - пытаться «починить» код, когда сервер просто говорит: «Притормози» или «Я временно прилег». Если ваш клиент начнет агрессивно долбиться в API после такой ошибки, вы рискуете получить вечный бан по IP или окончательно «добить» и без того страдающий сервер.
Правильная стратегия повторов - это не просто цикл while с попытками, а тонкий баланс между скоростью восстановления сервиса и уважением к ресурсам внешнего API. Давайте разберем, чем отличаются эти ошибки и как их обрабатывать, чтобы ваше приложение было отказоустойчивым.
| Характеристика | HTTP 429 (Too Many Requests) | HTTP 503 (Service Unavailable) |
|---|---|---|
| Причина | Превышен лимит запросов (Rate Limit) | Сервер перегружен или на техобслуживании |
| Кто виноват? | Клиент (слишком высокая интенсивность) | Сервер (внутренние проблемы) |
| Время решения | Обычно от 1 до 5 минут | От 30 минут до нескольких часов |
| Главное действие | Снизить частоту запросов, проверить квоты | Ждать восстановления инфраструктуры |
Почему нельзя обрабатывать 429 и 503 одинаково
Казалось бы, и там и там запрос не прошел, значит, надо попробовать еще раз. Но логика здесь принципиально разная. HTTP 429 is код состояния, указывающий на то, что пользователь отправил слишком много запросов к API за определенный промежуток времени. Это ваш «счетчик» обнулился. Если вы продолжите слать запросы с той же скоростью, сервер может расценить это как DDoS-атаку.
HTTP 503 is код ошибки, который означает, что сервер временно не может обработать запрос из-за перегрузки или проведения технических работ. Здесь проблема не в вас, а в «железе» или софте на той стороне. Попытки повторить запрос через секунду в 99% случаев бессмысленны, так как инфраструктура Google или Azure не чинится за мгновение.
Золотой стандарт: Exponential Backoff и Jitter
Если вы просто ставите паузу в 1 секунду между попытками, вы создаете проблему «громогласного стада» (thundering herd). Представьте, что тысяча ваших клиентов одновременно получили 503 ошибку. Если все они подождут ровно 1 секунду и снова ударят по серверу, они просто уронят его снова.
Чтобы этого избежать, используют Exponential Backoff (экспоненциальная задержка). Суть проста: время ожидания растет в геометрической прогрессии. Например: 1с $\rightarrow$ 2с $\rightarrow$ 4с $\rightarrow$ 8с $\rightarrow$ 16с. Это дает серверу пространство для «вдоха».
Но этого мало. Нужно добавить Jitter - случайный шум. Вместо ровно 4 секунд вы ждете 3.8 или 4.2. Это разносит запросы от разных клиентов во времени, превращая синхронный удар в плавный поток.
Работа с заголовком Retry-After
Многие разработчики игнорируют ответ сервера, считая его просто «ошибкой». Но в ответах 429 и 503 часто приходит заголовок Retry-After. Это буквально инструкция от сервера: «Не беспокой меня следующие 120 секунд».
Ваш код должен работать по такому алгоритму:
- Получили 429 или 503.
- Проверили наличие заголовка
Retry-After. - Если он есть - ждем ровно столько, сколько указано (в секундах или в виде даты).
- Если заголовка нет - включаем наш Exponential Backoff с Jitter.
Игнорирование этого заголовка в серьезных API (например, в сервисах Microsoft Fabric или Google Cloud) может привести к тому, что ваш API-ключ будет заблокирован на гораздо более длительный срок.
Практическая реализация: Python и Node.js
В Python для этого идеально подходит библиотека requests в связке с urllib3. Вы можете настроить стратегию повторов прямо при создании сессии. Укажите backoff_factor (например, 2), и библиотека сама будет увеличивать интервалы. В список status_forcelist обязательно добавьте [429, 500, 502, 503, 504].
В Node.js подход более гибкий. Рекомендуется создать отдельный класс-обработчик ошибок. Для 429 ошибки можно заложить базовую задержку в 60 секунд, а для 503 - в 300 секунд (5 минут). Это отражает реальность: восстановить лимит запросов быстрее, чем перезагрузить упавший кластер серверов.
Ошибки, которые стоят вам доступности сервиса
Самая частая ошибка - смешивать все 4xx ошибки в одну кучу. Если вы получили 400 (Bad Request), 401 (Unauthorized) или 404 (Not Found), никакие повторы не помогут. Запрос сформирован неправильно или ресурс удален. Повторение такого запроса 5 раз подряд - это просто трата трафика и ресурсов.
Еще один промах - бесконечные циклы. Всегда устанавливайте жесткий лимит попыток (обычно 3-5). Если после пятой попытки с экспоненциальным ростом сервер всё еще отвечает 503, значит, произошла серьезная авария. В этот момент приложение должно перестать пытаться и выдать пользователю вменяемое сообщение: «Сервис временно недоступен, мы уже чиним».
Как уменьшить количество ошибок на вашей стороне
Если вы владелец API и видите, что ваши пользователи постоянно ловят 429, проблема может быть не в них, а в вашей архитектуре. Вот что помогает:
- Кеширование: Оптимизируйте кеш, чтобы повторяющиеся запросы даже не доходили до основного сервера.
- Батчинг (Batching): Позвольте пользователям запрашивать данные пачками (например, 100 записей одним запросом вместо 100 отдельных вызовов).
- Балансировка нагрузки: Распределяйте трафик между несколькими инстансами, чтобы один сервер не становился «бутылочным горлышком».
- Автомасштабирование: Настройте запуск дополнительных ресурсов при резком скачке нагрузки.
Что делать, если Retry-After указывает слишком большое время (например, 1 час)?
В таком случае автоматический повтор в рамках одного HTTP-запроса невозможен из-за таймаутов. Правильным решением будет сохранить задачу в очередь (например, через RabbitMQ или Redis) с отложенным выполнением и уведомить пользователя, что операция будет завершена позже.
Почему 504 ошибка обрабатывается так же, как 503?
504 Gateway Timeout означает, что прокси-сервер не дождался ответа от вышестоящего сервера. Это часто бывает при временных сбоях сети или перезагрузке бэкенда. Поскольку причина внешняя и временная, стратегия Exponential Backoff здесь работает так же эффективно, как и при 503.
Безопасно ли использовать стандартные библиотеки для ретраев?
Да, библиотеки вроде urllib3 в Python или axios-retry в JS надежны, но они часто не учитывают специфику Retry-After из коробки. Рекомендуется добавить обертку, которая сначала проверяет этот заголовок, а затем передает управление стандартному механизму повторов.
Как протестировать обработку 503 ошибки, если сервер работает стабильно?
Лучший способ - использовать Mock-серверы (например, Prism или WireMock), которые можно настроить на выдачу случайных 503 кодов. В реальной среде это можно проверить через инструменты нагрузочного тестирования, имитируя перегрузку сервера.
Сколько попыток повтора считается нормой?
Отраслевым стандартом считается 3-5 попыток. Большее количество попыток обычно не помогает, если сервер действительно лежит, а лишь увеличивает нагрузку на систему и заставляет пользователя ждать слишком долго без результата.