АрхитектураАрхитектура ПОАрхитектор распределённых backend-систем

Разбор последствий: почему неограниченные повторные запросы после сбоя могут усилить отказ всей системы?

Разбор последствий: почему неограниченные повторные запросы после сбоя могут усилить отказ всей системы?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Неограниченные повторы создают retry storm: при замедлении или отказе зависимости клиенты увеличивают число запросов именно в момент, когда система уже перегружена. Это расходует рабочие потоки, соединения и CPU, повышает задержки и может превратить локальный сбой в каскадный отказ.

Повторы должны быть ограничены по числу попыток и общему времени, выполняться с экспоненциальной задержкой и случайным jitter. Для операций с побочными эффектами дополнительно требуется идемпотентность или иной механизм защиты от повторного выполнения.

Исторический контекст

Повторные запросы появились как практический способ переживать временные сетевые ошибки, краткие перегрузки и перезапуски зависимых компонентов. Сам по себе единичный повтор часто повышает успешность операции без усложнения бизнес-логики.

Проблема возникла при масштабировании распределённых систем: множество клиентов начинает повторять запросы одновременно. Поэтому управление повторами стало частью архитектуры отказоустойчивости, а не только деталью конкретного клиента.

Постановка проблемы

Пусть зависимый сервис отвечает медленно, но не сразу отказывает. Клиент удерживает рабочий поток до тайм-аута, затем отправляет повтор, пока предыдущая обработка ещё может продолжаться. При большом числе клиентов количество одновременно выполняющихся запросов растёт быстрее, чем способность зависимости их обработать.

Если каждый слой цепочки добавляет собственные повторы, один пользовательский запрос способен породить много попыток на нижнем уровне. В результате исчерпываются пулы соединений и потоки, увеличивается очередь, а восстановившийся сервис снова получает всплеск нагрузки.

Подробное решение

Повтор следует разрешать только для ошибок, которые действительно могут исчезнуть сами: например, кратковременного сетевого сбоя или временной недоступности зависимости. Постоянные ошибки, ошибки валидации и нарушения контракта повторением не исправляются.

Практическая схема включает несколько ограничений:

  • максимальное число попыток;
  • общий дедлайн операции, включая все повторы;
  • экспоненциальную задержку между попытками;
  • случайный jitter, чтобы клиенты не повторяли запросы синхронно;
  • ограничение числа одновременно выполняющихся запросов;
  • прекращение попыток при явно неисправной зависимости, например через размыкатель цепи.

Важно ограничивать не только число попыток, но и их совокупную стоимость. Повтор внутри нескольких вложенных слоёв особенно опасен: ответственность за повтор обычно должна находиться на одном хорошо определённом уровне, иначе множители попыток перемножаются.

Для операций записи необходимо учитывать дубликаты. Если повтор может привести к повторному списанию или созданию сущности, применяют идемпотентный ключ, дедупликацию или проверяемую бизнес-операцию. Это не устраняет retry storm, но предотвращает повторное применение побочного эффекта.

Компромисс состоит в том, что слишком агрессивные ограничения снижают шанс пережить краткий сбой, а слишком мягкие увеличивают нагрузку и задержку. Настройки выбирают с учётом бюджета задержки, характера операции, ёмкости зависимости и наблюдаемости: без метрик попыток, тайм-аутов и отказов правильную настройку проверить нельзя.

Ситуация из практики

Сервис оформления заказа обращается к сервису расчёта доставки. При замедлении расчёта каждый экземпляр клиента выполнял пять повторов, а API-шлюз и внутренний клиент делали это независимо. В результате один входящий запрос порождал до двадцати пяти обращений к расчёту доставки, после чего исчерпывались рабочие потоки обоих сервисов.

Рассматривались три варианта. Полный отказ от повторов уменьшал нагрузку, но делал систему чувствительной к кратким сетевым сбоям. Увеличение числа повторов повышало вероятность успеха отдельных запросов, однако усиливало перегрузку. Перенос всех повторов на один уровень с фиксированной задержкой был проще, но синхронные всплески сохранялись.

Выбрали один слой повторов с коротким дедлайном, экспоненциальной задержкой и jitter; для остальных ошибок включили немедленное завершение. При превышении порога ошибок зависимость временно исключалась из вызовов, а операция оформления переводилась в явно определённое состояние ожидания.

Такое решение сохранило возможность переживать краткие сбои, но ограничило нагрузку при длительной неисправности. Отдельно добавили защиту от повторного запуска расчёта по одному идентификатору операции, поскольку сетевой повтор не должен создавать несколько бизнес-результатов.

Что кандидаты часто упускают

  1. Чем retry storm отличается от обычного каскадного отказа?

Retry storm — это конкретный механизм усиления нагрузки повторными попытками. Каскадный отказ шире: он может возникать из-за исчерпания потоков, очередей, памяти или синхронных зависимостей даже без повторов. Повторы часто становятся ускорителем каскада, но не являются его единственной причиной.

  1. Достаточно ли экспоненциальной задержки без случайного разброса?

Нет. Если множество клиентов получили ошибку примерно одновременно и используют одинаковую последовательность задержек, они снова отправят запросы синхронными волнами. Jitter распределяет попытки во времени и уменьшает пиковую нагрузку; сама экспоненциальная формула этого не гарантирует.

  1. Почему общий дедлайн важнее простого лимита попыток?

Фиксированное число попыток не ограничивает суммарную задержку: при медленных ответах каждая попытка может занимать почти весь тайм-аут. Общий дедлайн связывает повторы с допустимым временем пользовательской операции и не позволяет устаревшему запросу продолжать занимать ресурсы после того, как его результат уже не имеет практической ценности.