Во время краткого сбоя облачного сервиса все реплики начинают повторять запросы одновременно. Как экспоненциальная задержка с добавлением случайного разброса предотвращает усиление нагрузки?
Экспоненциальная задержка увеличивает интервал между повторными попытками после каждого сбоя, а jitter добавляет случайный разброс к этому интервалу. Поэтому клиенты не повторяют запросы синхронно: пиковая нагрузка распределяется во времени, и временный сбой с меньшей вероятностью превращается в каскадный отказ.
Повторные попытки появились как способ переживать кратковременные сетевые сбои, перегрузку и временную недоступность зависимостей без немедленного отказа пользовательского запроса. Однако фиксированная задержка приводит к тому, что множество клиентов, обнаруживших сбой одновременно, повторяют запросы одновременно.
Экспоненциальная задержка уменьшает интенсивность повторных обращений, а случайный разброс устраняет синхронизацию. Такой подход применяется в клиентах облачных сервисов, брокерах сообщений, распределённых системах и сетевых библиотеках.
Предположим, несколько сотен экземпляров приложения одновременно получают таймаут от базы данных или объектного хранилища. Если каждый экземпляр немедленно повторит запрос, зависимость получит ещё больше трафика именно в момент восстановления.
При фиксированной задержке ситуация может повторяться волнами: клиенты ждут одинаковое время и снова создают общий пик. В результате растут очереди, увеличиваются таймауты, исчерпываются пулы соединений и начинают отказывать уже независимые компоненты.
После первой неудачи клиент ждёт небольшую задержку, после следующей — более длительную. Типичная последовательность может расти примерно как 1, 2, 4, 8 секунд, но должна иметь верхний предел, чтобы ожидание не становилось бесконечным.
Jitter изменяет рассчитанную задержку случайным образом. Например, вместо одинакового ожидания в 8 секунд разные экземпляры могут ждать немного меньше или больше. Это распределяет повторные запросы и снижает вероятность синхронного всплеска.
На практике важно ограничивать не только задержку, но и общее число попыток или retry budget. Повторная попытка должна укладываться в общий deadline операции; иначе внутренние ретраи будут продолжаться после того, как внешний запрос уже обречён на таймаут.
Повторять безопасно прежде всего идемпотентные операции: чтение, повторную проверку состояния или запись с устойчивым ключом идемпотентности. Без такой гарантии повтор POST-подобной операции может создать дубликат платежа, заказа или другого ресурса.
Ретрай не является универсальным лечением. Для постоянной ошибки авторизации, некорректного запроса или отсутствующего ресурса повторение бесполезно. Для перегруженной зависимости ретраи следует сочетать с таймаутами, ограничением параллелизма и circuit breaker, который временно прекращает обращения после превышения порога ошибок.
Слишком агрессивная задержка уменьшает доступность приложения, а слишком короткая — сохраняет риск перегрузки. Случайный разброс также требует корректной наблюдаемости: в метриках нужно различать исходные запросы, повторные попытки и окончательные ошибки.
Сервис оформления заказа обращается к облачному хранилищу за параметрами доставки. При кратком сетевом сбое каждая реплика делает несколько повторов через одинаковые две секунды. Хранилище восстанавливается, но получает синхронные волны запросов и продолжает отвечать с таймаутами.
Рассматривались три варианта. Полный отказ от повторов давал предсказуемую нагрузку, но ухудшал устойчивость к кратким сбоям. Фиксированный интервал сохранял простоту, однако не устранял синхронизацию. Экспоненциальная задержка с полным jitter, ограничением числа попыток и общим deadline уменьшала вероятность повторного пика, сохраняя шанс пережить короткий сбой.
Был выбран третий вариант, а для операций изменения состояния добавили ключ идемпотентности. В результате отдельные временные ошибки стали обрабатываться автоматически, а длительная недоступность зависимости быстрее приводила к контролируемому отказу, не занимая бесконечно рабочие потоки.
Если все клиенты начали работу одновременно и используют одинаковую формулу без случайного разброса, их следующие попытки всё равно будут синхронными. Экспоненциальный рост уменьшает частоту запросов, но не устраняет общие пики; для этого нужен jitter.
Сбой ответа не доказывает, что операция не была выполнена. Сервер мог принять запись, но клиент потерял ответ и повторил запрос. Поэтому повторять такие операции можно только при гарантированной идемпотентности, например с уникальным ключом операции, по которому сервер распознаёт дубликат.
Каждая внутренняя попытка расходует время и ресурсы внешнего запроса. Если ретраи не связаны с его deadline, приложение может продолжать работу над уже неактуальным запросом, удерживать соединения и потоки, а затем всё равно вернуть таймаут. Общий deadline позволяет прекратить попытки, когда полезный результат уже невозможно доставить вовремя.