Три попытки превратились в 27: как ограничить повторы запросов API
Повторы в модуле, библиотеке и промежуточном обработчике могут перемножаться. Считаем учебный пример и разбираем общий срок операции, задержки и условия повторной отправки.

В этой статье
При сбое магазин делает до трёх попыток отправки запроса. Его библиотека тоже умеет повторять запросы. Между ними стоит ещё один обработчик с такой же настройкой. На схеме всё выглядит заботливо: каждый уровень пытается пережить временную ошибку. У нижнего сервиса эта забота превращается в дополнительную нагрузку именно тогда, когда ему уже трудно.
Начать стоит с инвентаризации повторов по всей цепочке: кто повторяет, какие ошибки считает временными, сколько попыток делает и сколько времени имеет вся операция. Одна настройка в модуле магазина не описывает поведение HTTP-клиента, очереди и внешнего SDK одновременно.
Откуда берутся 27 обращений
В учебной модели есть три вложенных уровня. На каждом разрешены три попытки всего: первоначальная и две повторные. Каждая попытка верхнего уровня запускает до трёх попыток среднего; каждая из них — до трёх попыток нижнего. Если все вызовы завершаются ошибкой и повторы полностью исчерпываются, получаем 3 × 3 × 3 = 27 обращений к нижнему сервису.
Это верхняя граница выбранной упрощённой модели, а не измерение магазина. При раннем успехе, общем сроке выполнения или иной политике ошибок число будет меньше. Локальный перебор трёх вложенных циклов подтвердил 27. Если повторы оставить только на одном уровне с тем же пределом, в этой модели обращений будет не более трёх.
Особенно легко ошибиться в словах «три повтора» и «три попытки». Первое обычно означает ещё три запроса после исходного, то есть четыре всего; второе может включать исходный. Название параметра нужно проверять в документации конкретной библиотеки. Например, документация AWS SDK определяет максимальное число попыток с учётом первого запроса; это не правило наименования всех остальных клиентов.
Лимит попыток и срок операции нужны одновременно
Пусть одна попытка может занять две секунды, а между тремя попытками предусмотрены ожидания одну и две секунды. В нашем последовательном примере получается 2 + 1 + 2 + 2 + 2 = 9 секунд без дополнительного времени на подготовку. Тайм-аут две секунды не означает, что пользователь получит окончательный ответ через две секунды.
Общий срок операции ограничивает всю цепочку ожидания. Перед следующей попыткой проверяют оставшееся время, а вложенному вызову передают допустимый остаток, если протокол и библиотека это поддерживают. Документация gRPC описывает распространение дедлайна между вызовами. Для произвольного HTTP-клиента такая возможность не возникает сама: её наличие и реализацию проверяют отдельно.
Истечение ожидания на стороне клиента не доказывает отмену работы на сервере. Поэтому у операции создания заказа по-прежнему остаётся вопрос неопределённого результата и защиты от дублей. Общий дедлайн ограничивает ожидание, но не заменяет ключ повторяемости и способ узнать фактический итог.
Задержка со случайным разбросом не заменяет бюджет
Если тысяча клиентов после сбоя ждёт ровно одну секунду, они могут вернуться почти одновременно. Увеличение задержек с добавлением случайного разброса помогает разнести повторные обращения. Такую практику описывают Google SRE и документация механизмов повторов AWS. Конкретные интервалы выбирают по условиям API и допустимому времени операции.
Однако случайность меняет распределение во времени, а не установленный максимум попыток. Двадцать семь разрешённых обращений не превращаются в три только потому, что паузы стали разными. Поэтому полезно отдельно ограничивать повторы одной операции и общий поток повторных обращений к зависимому сервису.
При проектировании выбирают уровень, который знает достаточно о смысле ошибки и оставшемся времени, чтобы управлять повтором. Остальные уровни должны согласовать с ним свою политику. Нельзя просто отключить все нижние повторы на рабочей системе по общему совету: сначала нужно проверить возможности библиотек и поведение в тестовом контуре.
Повторять только то, что имеет шанс измениться
Неверный обязательный реквизит не исправится от ожидания. Отказ в доступе тоже требует выяснения причины, а не бесконечной отправки того же запроса. Временный сетевой сбой может допускать повтор, но для операции с побочным эффектом этого недостаточно: контракт должен позволять безопасно определить или повторить её результат.
Статус 429 означает ограничение частоты запросов. Ответ может содержать Retry-After; отсутствие этого заголовка не означает разрешение немедленно продолжать. Если поставщик сообщает время ожидания, его учитывают вместе с общим сроком операции. Когда ждать в текущем запросе уже нельзя, нужен предусмотренный процесс отложенной обработки или явный отказ, а не попытка обойти ограничение.
Правила ошибок берут из документации конкретного сервиса, включая его машинные коды. Не стоит автоматически приравнивать любой ответ одного HTTP-класса к одному сценарию повтора. Также не нужно менять ключ операции после каждого тайм-аута: это способ превратить неопределённый результат в несколько независимых команд.
Что измерить на приёмке
В безопасном тестовом контуре полезно смоделировать временный отказ, постоянную ошибку входных данных и медленный ответ. Для одной бизнес-операции посчитайте все фактические обращения, суммарное время и причину остановки. Отдельно проверьте, что истечение общего срока не запускает новую цепочку с полным бюджетом.
Результатом должна стать понятная граница: эта операция может обратиться к зависимости не более согласованного числа раз, ждать не дольше согласованного срока и повторяться только при перечисленных условиях. Приведённые числа учебные; реальный стек здесь не испытывался. Такая граница помогает пережить сбой, не поручая каждому слою самостоятельно его усилить.
On failure, the store makes up to three request attempts. Its library also supports request retries. Between them sits another handler with the same configuration. The diagram looks reassuring: each level attempts to survive a transient error. For the lower service, this care turns into additional load precisely when it is already struggling.
Start by auditing retries across the entire chain: who retries, which errors are considered transient, how many attempts are made, and what is the total time budget for the operation. A single setting in the store module does not describe the behavior of the HTTP client, the queue, and the external SDK simultaneously.
Where the 27 Requests Come From
The educational model has three nested levels. Each allows three attempts in total: the initial one plus two retries. Each attempt at the upper level triggers up to three attempts at the middle level; each of those triggers up to three attempts at the lower level. If all calls fail and retries are fully exhausted, we get 3 × 3 × 3 = 27 calls to the lower service.
This is the upper bound of the chosen simplified model, not a measurement of a real store. With early success, a total execution time limit, or a different error policy, the number will be lower. A local brute-force check of three nested loops confirmed 27. If retries are kept only at one level with the same limit, this model yields no more than three calls.
It is especially easy to confuse the phrases 'three repetitions' and 'three attempts'. The first usually means three additional requests after the initial one, totaling four; the second may include the initial request. The parameter name must be verified in the documentation for the specific library. For example, the AWS SDK documentation defines the maximum number of attempts including the first request; this is not a naming convention for all other clients.
Attempt Limits and Operation Duration Are Needed Simultaneously
Suppose one attempt takes two seconds, and there are waits of one and two seconds between the three attempts. In our sequential example, this results in 2 + 1 + 2 + 2 + 2 = 9 seconds without additional time for preparation. A two-second timeout does not mean the user will receive a final response within two seconds.
The overall operation deadline limits the entire wait chain. Before the next attempt, check the remaining time, and pass the allowable remainder to the nested call if the protocol and library support it. The gRPC documentation describes deadline propagation between calls. For an arbitrary HTTP client, this capability does not arise automatically: its existence and implementation must be verified separately.
A timeout on the client side does not prove that the work was cancelled on the server. Therefore, the order creation operation still faces the issue of an indeterminate result and the need for duplicate protection. A global deadline limits the wait time but does not replace the idempotency key or a method to determine the actual outcome.
Randomized Delay Does Not Replace a Budget
If a thousand clients wait exactly one second after a failure, they may return almost simultaneously. Increasing delays by adding random jitter helps spread out retry attempts. Google SRE and AWS retry mechanism documentation describe this practice. Specific intervals are chosen based on API conditions and the allowable operation time.
However, randomness changes the time distribution, not the configured maximum number of attempts. Twenty-seven allowed requests do not become three just because the pauses differ. Therefore, it is useful to separately limit retries for a single operation and the overall flow of retries to a dependent service.
During design, select the level that knows enough about the error meaning and remaining time to manage the retry. Other levels must align their policy with it. Do not simply disable all lower-level retries in a production system based on general advice: first, verify library capabilities and behavior in the test environment.
Retry Only What Has a Chance to Change
An invalid required field will not be fixed by waiting. Access denial also requires investigating the cause, not endlessly sending the same request. A temporary network failure may allow a retry, but for an operation with side effects, this is insufficient: the contract must allow safely determining or repeating its result.
The status 429 indicates a rate limit. The response may include Retry-After; the absence of this header does not mean you are immediately allowed to proceed. If the provider specifies a wait time, consider it alongside the total operation duration. When waiting within the current request is no longer possible, a predefined deferred processing workflow or an explicit failure is required, not an attempt to bypass the limit.
Error rules are derived from the specific service documentation, including its machine-readable codes. Do not automatically equate any response from a single HTTP class to a single retry scenario. Also, do not change the operation key after every timeout: this is a way to turn an uncertain result into multiple independent commands.
What to Measure During Acceptance Testing
In a safe test environment, it is useful to simulate a temporary failure, a persistent input error, and a slow response. For a single business operation, count all actual requests, the total time, and the reason for the stop. Separately verify that the total duration timeout does not trigger a new chain with a full budget.
The result should be a clear boundary: this operation may access a dependency no more than an agreed number of times, wait no longer than an agreed duration, and retry only under the specified conditions. The numbers provided are illustrative; the actual stack was not tested here. Such a boundary helps survive a failure without requiring each layer to independently strengthen its resilience.





Обсуждение 0
Делись опытом и задавай вопросы. Комментарии без ссылок появляются после проверки редактором.
Пока никто не написал. Начни обсуждение.