Кейс: экспоненциальный backoff без верхней границы держал клиента в цикле часами

Сервис уведомлений слал запросы во внешний платёжный провайдер. При ошибке — retry с экспоненциальной задержкой: 1с, 2с, 4с, 8с и так далее. Логика казалась стандартной и рабочей.

Провайдер лёг на несколько часов. Задержка между попытками честно росла по формуле, но верхнего предела в коде не было. Через два часа retry-интервал у части запросов дорос до значений в десятки минут — и застрял там: следующий рестарт сервиса из-за деплоя сбрасывал прогресс, и цикл начинался заново с 1 секунды, забивая провайдер по новой в момент, когда тот только начал восстанавливаться.

long delay = baseDelayMs * (1L << attempt); Thread.sleep(delay);

Проблема не в самом backoff, а в отсутствии cap и jitter. Без верхней границы задержка растёт неограниченно и непредсказуемо синхронизируется у разных инстансов сервиса, если у них общий момент старта retry.

Что спросят следом на собесе: как ограничить и рандомизировать backoff правильно. Ответ — cap на максимальную задержку (например 30 секунд) плюс jitter: случайный разброс вокруг расчётного значения, чтобы разные инстансы не долбили провайдера синхронными волнами в один и тот же момент.

Экспоненциальный backoff без cap и jitter — это не защита от каскадного отказа, а отложенная версия той же проблемы.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки