Кейс: экспоненциальный 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 — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки