Слепой цикл повторов — частая причина, по которой интеграция с чужим API вместо того, чтобы пережить кратковременный сбой, усугубляет его: продолжает долбить в уже неверный ключ, жжёт лимит запросов на заведомо обречённые вызовы или, того хуже, повторно списывает деньги за одну и ту же покупку. Правильный подход начинается не с паузы между попытками, а с классификации причины: не каждую стоит повторять, и не каждый повтор безопасен.
Три категории ошибок API
Временные — не связаны с содержанием запроса: обрыв сети, таймаут соединения, кратковременная перегрузка сервиса у провайдера. Тот же запрос с теми же параметрами через несколько секунд отработает штатно, потому что дефект был в канале или моменте времени, а не в самом запросе.
Постоянные — обратный случай: неверный ключ доступа, невалидный параметр, отсутствие прав на операцию. Такой вызов провалится одинаково и на первой попытке, и на сотой, потому что причина в самом запросе или настройке аккаунта. Повтор без исправления причины не даёт ничего, кроме лишней строки в логе.
Денежные — формально похожи на временные: недостаточно средств на балансе, нет доступного номера под нужную страну. Повторный запрос может сработать позже, после пополнения баланса или обновления стока, но повторять его вслепую, тем более саму операцию списания, нельзя — следующие разделы объясняют почему.
Что повторять, а что нет
Правило простое: имеет смысл повторять только то, что способно исправиться само за счёт времени. Сетевой сбой, таймаут, ответ 5xx или признак перегрузки — кандидаты на автоматический повтор по схеме бэкоффа. Неверный ключ, некорректный параметр, отказ по правам — повторять бессмысленно, пока причина не устранена вручную: нужно поправить настройку и отправить вызов заново один раз, а не крутить цикл. Нехватку баланса или стока показывают вызывающей стороне как отдельное состояние, и решение о повторе принимает бизнес-логика или пользователь, а не таймер. Где именно в модели «покупка по факту или на срок» чаще возникает нехватка стока, разобрано в статье OTP-активация против аренды номера.
Экспоненциальный бэкофф и цена слепого повтора
Экспоненциальный бэкофф — пауза перед следующей попыткой, растущая с каждым разом: условно секунда, затем две, четыре, восемь — до потолка в несколько попыток, обычно три-пять, после которого цикл прекращается и отказ передаётся выше как окончательный. К паузе стоит добавлять небольшой случайный разброс, чтобы множество клиентов, столкнувшихся с одним сбоем провайдера, не ударили по нему синхронной волной ровно в момент восстановления.
Без классификации на входе бэкофф не спасает: применённый к постоянной причине, он лишь растягивает во времени бесполезные вызовы, каждый из которых занимает слот в общем лимите аккаунта. Пока цикл впустую долбит в невалидный ключ, полезные обращения того же аккаунта — реальные активации, проверки статуса — упираются в тот же лимит и получают отказ уже по ограничению скорости. В автоматизации с параллельными воркерами, разобранной в статье про регистрации через API, один зависший в цикле повторов процесс способен посадить лимит для всех остальных.
Логи для разбора инцидента
После сбоя разбор идёт по логу, а не по памяти, поэтому записывать нужно достаточно, чтобы ответить на вопрос «это провайдер или наша сторона» без повторного воспроизведения проблемы. Минимальный набор: время вызова, эндпоинт, идентификатор запроса или активации, код и текст отказа от провайдера, номер попытки по счёту и параметры без секретов — ключи и токены в лог не попадают.
Отдельно стоит фиксировать не только ответ провайдера, но и то, как ваш обработчик классифицировал причину — как временную, постоянную или денежную. Если классификатор ошибся и постоянную причину по факту повторяли как временную, это видно только по такой записи, а не по одному коду ответа.
Идемпотентность денежных операций: как не списать дважды
Таймаут не говорит однозначно, выполнилась операция на сервере или нет: соединение могло оборваться уже после списания, но до того, как ответ дошёл до клиента. Повтор такого запроса без защиты выглядит для API как новая операция — и приводит к повторному списанию за одну и ту же покупку.
Ключ идемпотентности решает проблему: клиент генерирует уникальный идентификатор один раз на операцию и передаёт его при каждой попытке, включая повторы после таймаута. Сервер запоминает результат, выданный на этот ключ, и при повторном обращении с тем же значением возвращает его вместо повторного выполнения. Это единственный безопасный способ повторить денежный вызов — без ключа риск двойного списания остаётся всегда.
Частые вопросы
Нужно ли ретраить ошибку 429 так же, как таймаут?
Да, но с более длинной паузой: 429 означает не сбой, а исчерпанный лимит запросов, и мгновенный повтор только продлит ожидание для всех. Возьмите значение из заголовка ответа, если провайдер его отдаёт, либо начните бэкофф сразу с паузы в несколько секунд, а не с одной.
Можно ли автоматически повторять запрос на списание баланса при таймауте?
Не сам запрос — только операцию с ключом идемпотентности. Таймаут не говорит, выполнилась ли операция на сервере до обрыва связи; повтор с тем же ключом безопасен, потому что провайдер вернёт результат уже выполненной операции вместо повторного списания. Без ключа такой повтор рискует задвоить платёж.
Сколько попыток разумно закладывать в ретрай временной ошибки?
Как правило три-пять попыток с экспоненциальной паузой и общим потолком ожидания около 30-60 секунд. Больше попыток редко меняют исход: если провайдер не восстановился за этот интервал, проблема системная, и её стоит эскалировать, а не продолжать цикл.
Коды по категориям, заголовок ключа идемпотентности и параметры повторов для каждого эндпоинта — в документации API. Приём событий по вебхуку, который тоже опирается на повторы и идемпотентность, разобран в статье вебхуки: приём кодов без поллинга, базовые сценарии интеграции — в материале API аренды номеров.