После тайм-аута нельзя делать вывод, что API-операция не выполнилась. Сервер мог успеть создать заказ, платёж или задачу, а ответ потерялся по пути к клиенту. Повтор безопасен только тогда, когда операция идемпотентна по контракту либо повтор отправляется с тем же ключом идемпотентности, а сервер гарантированно распознаёт дубль.
Если такой гарантии нет, сначала найдите результат первой попытки по идентификатору операции, заказу или журналам. Слепой повтор POST-запроса с новым идентификатором может выполнить действие второй раз.
Почему после тайм-аута результат остаётся неизвестным
Тайм-аут описывает ожидание клиента или промежуточного узла, но не состояние бизнес-операции. Возможны разные варианты:
- запрос не дошёл до приложения;
- приложение получило запрос, но ещё не завершило его;
- изменение уже сохранено, но ответ не вернулся;
- шлюз вернул 504, хотя нижестоящий сервис позднее закончил работу;
- клиент прекратил ожидание раньше, чем сервер подготовил успешный ответ.
Поэтому сообщение «не дождались ответа» не равно сообщению «ничего не изменилось». Для операции записи нужен способ отличить повтор той же попытки от нового намерения пользователя.
Какие запросы можно повторять
В RFC 9110 идемпотентной называется семантика, при которой несколько одинаковых запросов имеют тот же ожидаемый эффект, что и один. Стандарт допускает автоматический повтор идемпотентного запроса после обрыва связи, но предостерегает от автоматического повтора неидемпотентного метода без дополнительной гарантии.
| Ситуация | Что делать после тайм-аута | |---|---| | GET или HEAD только читает данные | Обычно можно повторить, если реальная реализация не запускает побочные действия | | PUT устанавливает известное состояние | Повтор обычно сохраняет тот же итог, но нужно соблюдать контракт конкретного API | | DELETE удаляет тот же ресурс | Повтор не должен удалить что-то ещё; код ответа при второй попытке может отличаться | | POST или PATCH создаёт либо изменяет сущность | Не повторять автоматически без ключа идемпотентности или проверки результата | | Платёж, заказ, рассылка, начисление | Действовать только по правилам провайдера и сверять бизнес-результат |
Что проверить до повторного запроса
1. Сохраните данные первой попытки
Нужны адрес и метод, время с часовым поясом, клиентский идентификатор операции, ключ идемпотентности, безопасный отпечаток параметров и correlation ID, если API его возвращает. Не записывайте в обычный журнал токены, cookie, пароли, реквизиты и персональные данные.
2. Запросите состояние операции
Ищите результат по бизнес-идентификатору: номеру заказа, client reference, идентификатору платежа или отдельному status endpoint. Если сущность уже создана, новый запрос на создание не нужен.
3. Прочитайте контракт поставщика API
Проверьте, поддерживает ли endpoint idempotency key, где его передавать, сколько он хранится, как сервис отвечает на повтор и что происходит при тех же ключе и других параметрах. Сам заголовок без серверной поддержки не защищает от дубля.
Отдельно уточните правила для 429, 502, 503, 504, обрыва соединения и тайм-аута клиента. Повтор, разрешённый для временной сетевой ошибки, не обязан быть допустимым после ошибки валидации или авторизации.
4. Остановитесь при неопределённом результате
Если API не даёт проверить статус и не обещает дедупликацию, автоматический повтор изменяющей операции лучше остановить. Сначала нужна ручная сверка с базой, платёжной системой, очередью или журналом поставщика; затем — осознанный повтор либо компенсирующее действие.
Как должен работать ключ идемпотентности
Ключ относится не к сетевой попытке, а к одному бизнес-намерению. Пользователь один раз нажал «Создать заказ» — клиент создаёт один непрозрачный уникальный ключ, сохраняет его до отправки и использует тот же ключ во всех повторах этой операции. Для нового заказа нужен новый ключ.
На сервере защита должна охватывать весь путь:
- принять ключ вместе с запросом;
- связать его с клиентом, endpoint и отпечатком значимых параметров;
- атомарно зафиксировать ключ и изменение данных;
- при повторе вернуть прежний результат или понятный статус выполнения;
- отклонить тот же ключ с другим содержимым;
- хранить запись не меньше документированного окна возможных повторов.
Атомарность важна: если сначала создать заказ, а ключ записать позже, сбой между этими действиями снова оставит окно для дубля. Конкурентные запросы с одним ключом также должны сходиться к одной операции.
В AWS Builders’ Library описан подход с идентификатором, который задаёт клиент: одинаковые параметры ещё не доказывают, что намерение одно, ведь пользователь действительно может заказать две одинаковые сущности. Документация Stripe показывает конкретную реализацию, где повтор с тем же ключом возвращает сохранённый результат. Это примеры контрактов, а не универсальное поведение любого API.
Частые ошибки при реализации повторов
- создавать новый ключ при каждой сетевой попытке — сервер увидит новые операции;
- считать хеш параметров единственным идентификатором — два намеренно одинаковых заказа станут неразличимы;
- повторять все ошибки без ограничения — временный сбой превратится в лавину запросов;
- игнорировать Retry-After и правила поставщика;
- менять тело запроса, сохраняя прежний ключ;
- считать HTTP 200 доказательством правильного бизнес-результата;
- хранить только ответ, но не связь с фактическим изменением данных.
Повторы должны иметь ограниченный бюджет по времени и числу попыток, а пауза между ними — увеличиваться с небольшим случайным разбросом. Это снижает одновременную нагрузку, но не заменяет идемпотентность.
План действий, если дубль уже мог появиться
- Остановите автоматические повторы для проблемного endpoint.
- Соберите идентификатор операции, ключ, точное время и безопасные фрагменты журналов.
- Найдите все созданные сущности и связанные побочные действия: списания, письма, задачи, webhook.
- Определите канонический результат, не удаляя данные наугад.
- Выполните предусмотренную системой отмену, возврат или объединение.
- Добавьте дедупликацию и только затем верните автоматические повторы.
- Проверьте сценарий с потерянным ответом и двумя одновременными запросами.
Как связать повтор API с мониторингом
Для регулярного контроля выбирайте безопасный публичный GET или health endpoint без побочных эффектов. Подробнее о выборе точек и границах проверки — в статье «Мониторинг API: какие endpoint проверять и как читать результат».
Web-Puls может регулярно проверять доступность такого URL и сообщить, что он перестал отвечать. Мониторинг не должен отправлять заказы, платежи или авторизованные запросы и не подтверждает исход конкретной бизнес-операции: для этого нужны идентификатор, журналы и статус у поставщика.
Что сделать сейчас
Зафиксируйте для каждого изменяющего endpoint четыре вещи: какой идентификатор задаёт клиент, как проверяется статус, какие ошибки разрешают повтор и сколько живёт запись дедупликации. Затем отдельно протестируйте потерю ответа после успешной записи.
Если тайм-ауты уже приводят к неопределённым заказам или списаниям и нужен технический разбор, отправьте через форму профессиональной поддержки Web-Puls публичный адрес API, время и часовой пояс, ожидаемый результат, безопасные идентификаторы и недавние изменения. Не передавайте токены, cookie, пароли и персональные данные.