Получить доступ

Клиент отправил POST для создания заказа, не дождался ответа из-за таймаута и повторил запрос. Как правильно не допустить второго заказа?

MiddleПроектирование API
Ещё спрашивают так:
  • Как защититься от двойного заказа при повторе запроса?
  • Как работает Idempotency-Key?
Коротко

Клиент генерирует ключ идемпотентности (UUID) на операцию и передаёт его в заголовке; сервер сохраняет ключ и результат в одной транзакции с созданием заказа и на повтор возвращает сохранённый ответ.

  • Уникальный индекс по ключу защищает от параллельных повторов
  • Тот же ключ с другим телом — ошибка 422, а не молчаливый повтор
  • Ключи хранят ограниченное время (сутки–неделя)

А ещё Та же идея для сообщений из брокера: таблица обработанных id

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

POST не идемпотентен, а клиент после таймаута не знает, создан ли заказ. Повторить нельзя — будет два заказа; не повторить — заказ может потеряться. Решение — сделать операцию идемпотентной явно.

Схема:

  1. клиент генерирует UUID для конкретного намерения («оформить эту корзину») и отправляет Idempotency-Key: <uuid>. При повторе — тот же ключ;
  2. сервер в одной транзакции: вставляет ключ в таблицу (уникальный индекс), создаёт заказ, сохраняет ответ рядом с ключом;
  3. если ключ уже есть — возвращает сохранённый ответ, не создавая заказ;
  4. если запрос с этим ключом ещё выполняется (параллельный повтор) — ждать или ответить 409 «в обработке».

Критичные детали:

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

Так работают API Stripe, ЮKassa и других платёжных систем. Для сообщений из брокера с доставкой «хотя бы один раз» — та же идея: id сообщения в таблице обработанных в одной транзакции с эффектом.

Пример

BEGIN;
INSERT INTO idempotency_keys(key, request_hash) VALUES ($1, $2);   -- уникальный индекс
INSERT INTO orders(...) VALUES (...) RETURNING id;
UPDATE idempotency_keys SET response = $3 WHERE key = $1;
COMMIT;
-- повтор: INSERT упадёт по уникальности → SELECT response FROM idempotency_keys WHERE key = $1

Что хотят услышать

  • Ключ от клиента на намерение, а не на попытку.
  • Сохранение ключа и результата в одной транзакции с операцией.
  • Обработка параллельных повторов и несовпадающего тела.
  • Перенос идеи на обработку сообщений.

Частые ошибки

  • Генерировать ключ на сервере или на каждую попытку.
  • Дедуплицировать по совпадению содержимого запроса.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Клиент передаёт Idempotency-Key, сервер сохраняет ключ вместе с результатом в одной транзакции с бизнес-эффектом и при повторе возвращает сохранённый ответ
  • ✗Заменить POST на PUT — тогда повтор станет безопасным сам по себе
  • ✗Отключить ретраи на клиенте и увеличить таймаут
  • ✗Сравнивать тела запросов и отклонять полностью совпадающие в течение минуты

Что спросят дальше

  • Сколько хранить ключи идемпотентности?
  • Как та же идея работает при at-least-once доставке из брокера?

Разбор темы

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем