Тайм-аут оставляет результат неизвестным
В понедельник утром корпоративная система отправляет команду на создание заказа. Через три секунды клиент прекращает ждать. В журнале отправителя — тайм-аут, у пользователя — кнопка «Повторить». Но тайм-аут сообщает только одно: клиент не получил ответ вовремя.
Он не сообщает, что произошло на сервере. Запрос мог не дойти. Сервер мог принять его, записать заказ и потерять ответ на обратном пути. Мог сохранить промежуточное состояние и продолжить работу асинхронно. Поэтому автоматическое правило «нет ответа — отправить ещё раз» превращает сетевую неопределённость в два заказа, две заявки или два списания.
RFC 9110 разрешает автоматически повторять идемпотентный запрос после разрыва связи, потому что повтор имеет тот же предполагаемый эффект. Для неидемпотентного метода стандарт требует отдельного знания: клиент должен понимать, что операция фактически идемпотентна, либо уметь доказать, что первый запрос не был применён.
В русскоязычной документации Yandex Cloud приведён именно этот пограничный случай. Клиент отправил команду, сервер выполнил её, но ответ потерялся. Повтор без ключа может снова изменить ресурс. Повтор с тем же ключом возвращает состояние уже созданной операции.
Первое архитектурное решение поэтому звучит не «сколько раз ретраить», а «как клиент узнает результат после потери ответа».
Идемпотентность начинается с бизнес-операции
Идемпотентность — это не обещание одинаковых HTTP-ответов и не магическое свойство одного заголовка. Это правило: повтор одного и того же намерения не должен второй раз менять бизнес-состояние.
Уровни здесь легко перепутать:
- HTTP-метод задаёт общую семантику.
GET,PUTиDELETEсчитаются идемпотентными по предполагаемому эффекту, аPOST— нет по умолчанию. - API-операция уточняет прикладное действие. Например,
POST /ordersможно сделать безопасным для повтора отдельным контрактом. - Бизнес-операция определяет, что считать «тем же самым»: один заказ клиента, одну выплату, одну заявку или один запуск процесса.
- Техническая попытка — каждый физический запрос, которых у одной бизнес-операции может быть несколько.
Idempotency-Key связывает попытки только тогда, когда клиент создаёт один ключ на одно намерение, повторяет его без изменений и не использует для новой операции. Сервер со своей стороны должен хранить область ключа, сверять содержимое запроса и возвращать уже известный результат.
При этом Idempotency-Key нельзя называть универсально стандартизированным HTTP-заголовком. Соответствующий Internet-Draft IETF истёк 18 апреля 2026 года и не стал RFC. Yandex Cloud и другие провайдеры реализуют собственные договорённости. Значит, имя поля, формат ключа, срок хранения и ответы на конфликт должны быть записаны в документации конкретного API.
Есть и ещё одна граница. RFC определяет идемпотентность по намерению клиента. Сервер вправе создать отдельную техническую запись журнала для каждого повтора. Это нормально, если заказ, платёж или заявка возникают не более одного раза и команда может связать все попытки с одним итогом.
Матрица решений после ошибки
Код ответа полезен, но не принимает решение в одиночку. Архитектору нужны одновременно четыре условия:
- Что известно о результате первой попытки.
- Является ли бизнес-операция безопасной для повтора.
- Считает ли конкретный API ошибку временной.
- Не исчерпан ли общий бюджет времени и попыток.
| Состояние клиента | Что известно | Можно ли повторить | Действие |
|---|---|---|---|
| Соединение не установлено | Сервер, вероятно, не получил запрос, но транспорт не всегда даёт достаточное доказательство | Только по правилам клиента и операции | Проверить класс сетевой ошибки; для команды использовать тот же ключ и общий дедлайн |
| Тайм-аут или разрыв до ответа | Результат неизвестен: операция могла завершиться | Да, только если операция идемпотентна или защищена ключом | Повторить с тем же ключом либо запросить состояние операции |
408 Request Timeout |
Сервер или промежуточный узел не дождался запроса | Только если контракт API разрешает | Не считать код универсальным разрешением; сохранить тот же ключ |
429 Too Many Requests |
Превышен лимит запросов | Обычно да для безопасной операции | Уважать Retry-After, применить задержку и не создавать новый ключ |
500, 502, 503, 504 |
Возможна временная серверная или сетевая ошибка; момент изменения состояния не всегда известен | Только по контракту API и при идемпотентности | Ограниченный повтор с задержкой; при Retry-After ждать указанное время |
400, 401, 403 |
Запрос, аутентификация или права требуют изменения | Нет без исправления причины | Исправить данные, токен или права; не расходовать бюджет повторов тем же запросом |
404 или 409 |
Значение зависит от предметной модели: ресурс отсутствует или состояние конфликтует | Не автоматически | Прочитать прикладной код и определить отдельное действие: завершить, перечитать состояние или исправить команду |
2xx |
Сервер принял или завершил операцию | Повтор не нужен | Сохранить результат; для 202 перейти к проверке состояния по идентификатору операции |
RFC 6585 определяет 429 как ограничение частоты и допускает Retry-After. RFC 9110 позволяет серверу тем же полем сообщить задержку при 503. В таблице Yandex Cloud 429 означает превышение лимита, а 503 прямо сопровождается рекомендацией повторить запрос через несколько секунд. Но эта таблица — контракт одного семейства API, а не лицензия повторять любой 500 в любой системе.
Полезное правило: постоянная ошибка должна быстро завершать операцию, временная — давать ограниченный шанс на восстановление, неизвестный результат — вести к тому же ключу или отдельной проверке статуса.
Девять свойств надёжного API-контракта
До реализации критичной команды заполните девять строк. Если хотя бы одна остаётся «решим в коде», поведение при сбое ещё не спроектировано.
| Свойство | Что зафиксировать | Проверка контракта |
|---|---|---|
| Единица идемпотентности | Какое бизнес-намерение считается одной операцией | Две команды с разными намерениями никогда не получают один ключ |
| Владелец ключа | Кто создаёт ключ и когда | Клиент создаёт ключ до первой отправки и сохраняет для всех попыток |
| Область ключа | Метод, маршрут, организация, пользователь или другой контекст | Одинаковые строки в разных областях не конфликтуют случайно |
| Отпечаток запроса | Какие нормализованные поля связываются с ключом | Тот же ключ с другим значимым телом отклоняется как конфликт, а не исполняется |
| Атомарная защита | Где обеспечивается уникальность | Конкурентные запросы не проходят проверку одновременно |
| Состояния операции | Например: processing, succeeded, failed |
Для каждого состояния определён публичный ответ повторному клиенту |
| Восстановимый результат | Код, тело ответа или идентификатор операции | Повтор получает известный исход, а не только сообщение «дубль подавлен» |
| Срок хранения | Сколько ключ и результат доступны после завершения | Клиент знает окно безопасного повтора и поведение после его истечения |
| Ошибки и наблюдаемость | Повторяемые коды, Retry-After, номер попытки, общий идентификатор |
Дежурный может связать все попытки с одной операцией без чтения тела запроса |
Интервалы и числа нельзя выбирать по шаблону. TTL ключа должен покрывать максимальное окно, в котором клиент вправе повторить команду или восстанавливать результат. Общий дедлайн должен учитывать пользовательское ожидание и SLA зависимого сервиса. Число попыток ограничивается так, чтобы восстановление одного клиента не создавало перегрузку для всех остальных.
Как сервер не допускает дубль
Минимальная серверная последовательность выглядит так:
- Проверить запрос и определить область ключа.
- Рассчитать отпечаток значимого содержимого.
- Атомарно зарегистрировать комбинацию «область + ключ» со статусом
processing. - При конфликте сравнить отпечаток и состояние уже известной операции.
- Выполнить бизнес-изменение один раз.
- Сохранить финальный статус и восстановимый результат.
- На повтор вернуть этот результат или понятное состояние незавершённой операции.
В PostgreSQL уникальное ограничение обеспечивает уникальность значения или комбинации значений и создаёт уникальный B-tree индекс. Это хорошая последняя линия защиты от гонки двух запросов. Но индекс не решает весь контракт. Он не знает, что тот же ключ пришёл с другим телом, сколько хранить результат и что отвечать, пока первая попытка ещё выполняется.
Если бизнес-запись и состояние ключа находятся в одной базе, их по возможности фиксируют одной транзакцией. Если операция вызывает внешнюю систему, общей транзакции обычно нет. Тогда нужен явный автомат состояний: зафиксировать намерение, надёжно доставить внешнее действие, записать полученный результат и уметь сверить незавершённые операции. Задача не в обещании «ровно один сетевой запрос», а в доказуемом ограничении «не более одного бизнес-эффекта» и восстановлении неизвестного исхода.
Конкурентный повтор также требует ответа. Возможные договорённости: вернуть 202 со ссылкой на состояние, дождаться первой операции в пределах короткого лимита или сообщить конфликт с тем же идентификатором. Выбор зависит от длительности команды, но он должен быть одинаковым для всех клиентов и описан до разработки.
Повторы должны быть ограничены и видимы
Повтор — это дополнительная нагрузка в момент, когда зависимый сервис уже испытывает проблему. Поэтому клиенту нужны:
- тайм-аут одной попытки;
- общий дедлайн всей операции;
- максимальное число повторов;
- экспоненциальная задержка со случайным разбросом;
- поддержка
Retry-After; - остановка на постоянной ошибке;
- один уровень, который отвечает за повторы, чтобы несколько библиотек не умножали попытки друг друга.
Официальная стратегия Google Cloud Storage формулирует переносимый принцип: экспоненциальная задержка со случайным разбросом применяется только тогда, когда одновременно выполнены критерий повторяемой ошибки и критерий идемпотентности. Конкретные интервалы и настройки библиотек относятся к продукту Google Cloud; копировать их в корпоративный SLA без расчёта не нужно.
Наблюдаемость должна различать бизнес-операцию и физические отправки. OpenTelemetry рекомендует добавлять http.request.resend_count для каждой повторной отправки. В русскоязычной документации Yandex Cloud Monium каждая попытка оформляется отдельным дочерним спаном, чтобы было видно, какие попытки завершились ошибкой и какая стала успешной.
В трассировке и журнале полезно иметь идентификатор бизнес-операции, корреляционный идентификатор обмена, номер попытки, маршрут, класс ошибки, длительность и финальный исход. Тело запроса, токены, персональные и коммерческие данные туда не переносят. Ключ идемпотентности также не должен содержать бизнес-смысл: случайный идентификатор безопаснее номера договора или адреса клиента.
После полезного ответа можно выбирать способ реализации. Каталог решений НТА показывает направления корпоративной автоматизации, а BPMSoft Конструктор — платформенный контур для процессов и расширений. Но платформа не отменяет контракт: тайм-аут, повтор, состояние и восстановление нужно определить для каждого критичного обмена. Если требуется внешняя архитектурная проверка, команда НТА может разобрать одну операцию по этой матрице до начала реализации.
Тест понедельником
Возьмите одну команду с необратимым эффектом: создание заказа, проведение выплаты, регистрацию заявки или запуск процесса. На стенде прервите соединение после отправки запроса, но до получения ответа. Затем отправьте тот же запрос с тем же ключом.
Проверка пройдена, если команда может доказать пять вещей:
- В бизнес-данных появился не более чем один результат.
- Повтор связан с первой попыткой и не создаёт новую операцию.
- Клиент получает сохранённый результат или идентификатор состояния.
- В трассировке видны обе физические попытки и один финальный исход.
- Дежурный может восстановить состояние без ручного редактирования базы.
Затем повторите сценарий для 429, временного 503, постоянного 400 и конкурентных запросов с одним ключом. Если поведение приходится угадывать по реализации, контракт ещё не готов.
Будущее выдерживает понедельник не тогда, когда API ответил 200 на демонстрации. Оно работает, когда связь оборвалась в неудобный момент, а система всё равно сохранила один результат, объяснила своё состояние и дала безопасный путь восстановления.
Важное уведомление
Материал носит информационный характер и не является индивидуальной консультацией, проектной документацией, инструкцией по внедрению или гарантией результата. Применимость описанных подходов зависит от процессов, систем, данных, требований безопасности и иных условий конкретной организации. Перед внедрением или изменением ИТ‑систем необходимо самостоятельно оценить риски и привлечь профильных специалистов.
«В понедельник утром корпоративная система отправляет команду на создание заказа.»
- 01Тайм-аут означает неизвестный результат, а не гарантированный отказ: повтор команды безопасен только при явной идемпотентности или проверке состояния.
- 02Надёжный контракт связывает один ключ с одним намерением, проверяет тело, атомарно защищает уникальность, хранит исход и описывает конкурентный повтор.
- 03Коды ошибок, задержка и число попыток зависят от конкретного API и SLA; универсального правила «ретраить все 5xx» нет.
