Полностью упавший API иногда даже удобнее. Все запросы быстро ошибаются, alert очевиден. Гораздо веселее сервис, который отвечает успешно в семи случаях из десяти, в двух висит до timeout, а в одном возвращает 503.

Вот здесь появляются три слова, которые любят складывать в один мешок: timeout, retry и circuit breaker. Они решают разные проблемы. Если смешать их без контракта, можно превратить частичный сбой в retry storm. Retry webhook безопасен только тогда, когда повторяемая операция имеет понятный deadline и не создаёт второй side effect.

Я бы сначала определил, сколько времени бизнес-операция вообще имеет смысл ждать. Потом — какие ошибки можно повторять. И только после этого добавлял автоматику.

Timeout ограничивает ожидание, а не исправляет сбой

Без timeout один зависший upstream удерживает worker, connection и пользовательский запрос неопределённо долго. Timeout задаёт границу: после неё caller прекращает ждать ответ.

Но timeout не доказывает, что удалённая операция не произошла. Сервер мог сохранить данные и потерять response. Поэтому после timeout нельзя автоматически повторять side effect, если он не защищён idempotency.

Я задаю timeout исходя из общего latency budget процесса. Если клиент ждёт ответ пять секунд, один внутренний API не может получить десять минут просто потому, что его default такой.

Проверяемые источники: AWS — Retry with backoff pattern · Stripe — Idempotent requests

Retry нужен для transient errors, а не для любого красного статуса

Временная сетевой ошибка, 429 или часть 5xx могут восстановиться при следующей попытке. Ошибка валидации payload не станет лучше после третьего повторения.

AWS рекомендует retry with backoff для transient failures и отдельно предупреждает о влиянии на idempotency. Я бы составил явный список retryable conditions вместо правила `if error => retry`.

Для каждой операции также нужен лимит попыток и общий retry budget по времени. Иначе workflow может технически «стараться» дольше, чем результат имеет бизнес-смысл.

Проверяемые источники: AWS — Retry with backoff pattern

MEDIA FRAME · PLACEHOLDERИллюстрация будет добавлена позже
Retry webhook: timeout, backoff и circuit breaker при сбое API · временная заглушка

Exponential backoff без jitter всё ещё создаёт стадо

Если тысяча workers получила 503 одновременно и каждый повторяет через одну секунду, upstream получает новый пик ровно через секунду. Через две — следующий.

Exponential backoff увеличивает интервалы. Jitter добавляет случайность и разводит клиентов по времени. Это уменьшает синхронные волны нагрузки при восстановлении сервиса.

Параметры зависят от API и SLA. Я бы не копировал универсальные `1s, 2s, 4s` без понимания rate limits и бизнес-дедлайна.

Проверяемые источники: AWS — Retry with backoff pattern

Circuit breaker прекращает бесполезные вызовы

Когда upstream стабильно падает, продолжать каждый запрос через полный timeout дорого. Circuit breaker отслеживает failures и временно переводит вызов в open state: новые операции быстро получают controlled failure вместо похода в больной сервис.

Через заданный период breaker разрешает ограниченную проверку. Если сервис восстановился — закрывается. Если нет — остаётся open. AWS описывает этот pattern именно как защиту от повторяющихся вызовов сервиса, который вероятно продолжит падать.

Breaker не является business fallback. Он сообщает: этот dependency сейчас недоступен. Дальше workflow должен решить, поставить операцию в очередь, показать пользователю статус или передать человеку.

Проверяемые источники: AWS — Circuit breaker pattern

После retry budget нужен отдельный terminal state

Худший вариант — оставить execution в бесконечном `retrying`. Никто не знает, завершится ли процесс. Пользователь тоже.

Я бы после исчерпания бюджета переводил операцию в `failed_recoverable` или конкретную dead-letter queue. Там есть correlation ID, последняя ошибка, количество попыток, следующий допустимый action и owner.

Если операция чувствительна ко времени, terminal state может быть окончательным. Уведомление о встрече через сутки технически можно отправить. Только уже незачем.

Проверяемые источники: AWS — Retry with backoff pattern · AWS — Circuit breaker pattern

Проверять устойчивость нужно искусственным сбоем

На staging я бы задержал upstream дольше timeout, вернул серию 503, затем восстановил сервис. Потом повторил событие и посмотрел, не появилось ли два side effects.

Нужно увидеть, что retries соблюдают budget, breaker открывается, queue не теряет operation state, а recovery не создаёт дублей. Отдельно — что alert сообщает о проблеме раньше клиента.

Если система переживает только happy path, это ещё демо. Production начинается там, где сбой имеет заранее выбранный исход.

Проверяемые источники: AWS — Retry with backoff pattern · AWS — Circuit breaker pattern · Stripe — Receive Stripe events in your webhook endpoint

Retry budget должен учитывать зависимую цепочку

Один workflow редко вызывает единственный API. Если сервис A уже потратил четыре секунды из пятисекундного budget, сервис B не должен начинать собственные пять retries по две секунды.

Я бы передавал deadline или оставшийся budget вниз по цепочке. Каждый компонент выбирает timeout и число попыток внутри общего времени. Это защищает от каскада, где локально разумные retries делают end-to-end операцию бесконечной.

Для async flow правило мягче, но бизнес-дедлайн всё равно существует. Очередь не отменяет момент, после которого результат уже не нужен.

Проверяемые источники: AWS — Retry with backoff pattern · AWS — Circuit breaker pattern

Короткие вопросы перед production

Какие ошибки API стоит повторять автоматически?

Только те, которые считаются временными для конкретного сервиса: часть network errors, rate limiting и некоторые 5xx. 4xx из-за неверного payload обычно повторять бессмысленно.

Зачем circuit breaker, если есть retry?

Retry пытается восстановить отдельную операцию. Circuit breaker замечает, что dependency системно болен, и временно прекращает новые бесполезные вызовы.

Можно ли retry POST-запрос?

Можно, если операция имеет idempotency guarantee или иным способом безопасна при повторе. После timeout нельзя предполагать, что первый POST точно ничего не сделал.

Нормальная схема скучная: timeout ограничивает ожидание, retry работает только для transient failure, backoff не бьёт сервис толпой, breaker прекращает бессмысленные вызовы, после бюджета есть terminal state. Если это всё можно проверить искусственным сбоем — уже похоже на production.

Смежные контуры, которые стоит прогнать отдельно для «retry webhook»: Что происходит с бизнес-процессом, когда AI недоступен · Как перенести AI-автоматизацию с прототипа в рабочую систему · n8n или собственный backend: что выбрать для AI-автоматизации.