Короткий ответ
Retry storm возникает, когда повторные попытки при таймауте увеличивают общую нагрузку на уже перегруженную систему, усугубляя замедление и вызывая ещё больше таймаутов и повторов по цепочке; решение — ограничить и "размазать" повторы (exponential backoff с jitter), добавить circuit breaker, разрывающий цепочку при устойчивых отказах, и убрать циклическую синхронную зависимость между сервисами.
Как это работает подробнее
при небольшом замедлении БД запросы начинают превышать таймаут, оба сервиса запускают retry почти одновременно, что умножает количество запросов к уже перегруженной БД и друг к другу, создавая положительную обратную связь — нагрузка растёт лавинообразно, пока система не "захлёбывается" полностью, даже если первоначальное замедление было небольшим и кратковременным.
Пример / кейс
Ожидаемое поведение: при временном замедлении БД повторные запросы от разных клиентов будут размазаны во времени за счёт джиттера, не создавая синхронного всплеска; в сочетании с circuit breaker, который временно останавливает запросы при устойчивой деградации, нагрузка на БД не должна лавинообразно расти, а должна плавно снижаться по мере восстановления.
Пример кода
import random
import time
def call_with_backoff(fn, max_attempts=4, base_delay=0.2):
for attempt in range(max_attempts):
try:
return fn()
except TimeoutError:
if attempt == max_attempts - 1:
raise
delay = base_delay * (2 ** attempt) + random.uniform(0, 0.2)
time.sleep(delay) # экспоненциальная задержка + джиттер вместо фиксированного интервалаЧто использовать на практике
- Практический подход. Exponential backoff с случайным джиттером размазывает повторные попытки во времени, не давая им накладываться синхронно; circuit breaker после нескольких подряд неудачных попыток временно перестаёт слать запросы к сбоящей зависимости, давая ей время восстановиться, и периодически пробует "половинный" запрос для проверки восстановления. Циклическая синхронная зависимость (А вызывает Б, Б вызывает А) — отдельная архитектурная проблема, которую стоит устранять через асинхронные очереди или устранение цикла в принципе.
Подводные камни
- Частые ошибки. Использовать фиксированный интервал retry без backoff и джиттера, из-за чего повторы синхронизируются и усиливают пиковую нагрузку; не ограничивать максимальное число попыток или общий бюджет времени на retry, из-за чего запросы продолжают копиться; добавлять retry без circuit breaker, когда зависимость устойчиво недоступна — в этом случае retry только продлевает и усугубляет инцидент, не давая системе восстановиться.