Вопрос 8 · Раздел 6

Является ли POST идемпотентным?

Согласно спецификации HTTP, метод POST официально неидемпотентен. Это означает, что повторный вызов POST с теми же данными может привести к другому результату.

Версии по языкам: English Russian Ukrainian

Junior Level

Нет, POST не является идемпотентным.

Согласно спецификации HTTP, метод POST официально неидемпотентен. Это означает, что повторный вызов POST с теми же данными может привести к другому результату.

POST = действие. Каждое действие — новое. Два нажатия “Купить” = две покупки. В серьёзных системах это решают специальным ключом идемпотентности — о нём ниже.

Почему POST не идемпотентен?

POST обычно используется для:

  1. Создания ресурсов: POST /orders создаст новый заказ. Два вызова = два заказа
  2. Обработки данных: POST /emails/send отправит два письма
  3. Добавления данных: Каждый вызов добавляет что-то новое
POST /orders       → Создан заказ #1
POST /orders       → Создан заказ #2  (другой результат!)
POST /orders       → Создан заказ #3  (ещё один результат!)

Что это значит на практике?

  • POST нельзя автоматически повторять при сетевых ошибках
  • Каждый POST может создать новый побочный эффект
  • Браузеры предупреждают при повторной отправке POST-формы

Middle Level

RFC 9110 и неидемпотентность POST

POST предназначен для обработки данных в соответствии с логикой ресурса. Это фундаментальная особенность, которая определяет поведение браузеров, прокси-серверов и программистов.

Паттерн PRG (Post/Redirect/Get)

Классическое решение проблемы неидемпотентности POST в веб-интерфейсах:

  1. Пользователь жмёт “Оплатить” (POST)
  2. Сервер обрабатывает платёж и шлёт код 303 See Other с URL страницы успеха
  3. Браузер делает GET на страницу успеха

Результат: При нажатии F5 повторяется безопасный GET, а не опасный POST со списанием денег.

POST для поиска

Когда GET-запрос слишком длинный (много фильтров), используют POST /search. Технически он идемпотентен (только чтение), но семантически это POST.

Реализация идемпотентности для POST

В Enterprise-системах (Stripe, Adyen, AWS) POST делают идемпотентным с помощью Idempotency-Key:

  • Клиент генерирует UUID и передаёт его в заголовке
  • Сервер использует Distributed Lock (Redis) по ключу user_id + idempotency_key
  • Если запрос уже в процессе — 409 Conflict
  • Если уже завершён — возвращаем сохранённый результат

Senior Level

Exactly-once delivery

В системах с очередями (Kafka, RabbitMQ) POST часто является точкой входа. Без идемпотентности невозможно гарантировать “ровно одну” обработку сообщения при повторных отправках (retries) продюсером.

Database Unique Constraints

Самый дешёвый способ обеспечить идемпотентность POST — уникальный индекс по бизнес-ключам (например, order_number). Однако это переносит логику на уровень БД.

Паттерн Idempotency-Key (детали)

  • Используется Distributed Lock (напр. Redis) по ключу user_id + idempotency_key
  • Если запрос с таким ключом уже в процессе — 409 Conflict
  • Если уже завершён — возвращаем сохранённый результат (201 Created преобразуется в 200 OK при повторе)

Edge Cases

Когда неидемпотентность POST — это фича

Когда пользователь создаёт новый комментарий, каждый POST должен создать новую запись. Неидемпотентность здесь — желаемое поведение, а не проблема.

POST для поиска

Для таких случаев предложен метод QUERY (черновик, пока не является стандартом). На практике используют POST /search.

Partial Failure

Если POST должен создать 3 сущности, и создал только 2? При повторе с тем же ключом сервер должен уметь “досоздать” недостающее или вернуть ошибку.

Диагностика и Мониторинг

  • Double-Submit Detection: Мониторинг запросов с одинаковым телом от одного пользователя в течение короткого окна (500мс) помогает выявить баги UI
  • Header: X-Retry-Count: Полезно добавлять на клиенте, чтобы сервер понимал, является ли POST первой попыткой или результатом автоматического ретрая
  • Отслеживайте количество повторных POST-запросов — это индикатор проблем с сетью или багов на клиенте

🎯 Шпаргалка для интервью

Обязательно знать:

  • POST официально неидемпотентен по спецификации HTTP (RFC 9110)
  • Каждый POST может создать новый ресурс или побочный эффект (5 POST /orders = 5 заказов)
  • POST нельзя автоматически повторять при сетевом тайм-ауте — риск двойных оплат
  • Паттерн PRG (Post/Redirect/Get) решает проблему повторной отправки: POST → 303 → GET
  • Idempotency-Key (UUID в заголовке) + Redis-lock делают POST идемпотентным
  • Unique Constraints на уровне БД — самый дешёвый способ обеспечить идемпотентность POST
  • Неидемпотентность POST — это фича, когда создаются новые комментарии, заказы, платежи

Частые уточняющие вопросы:

  • Как Stripe делает POST идемпотентным? — Клиент передаёт Idempotency-Key, сервер хранит результат в Redis
  • Что такое Exactly-Once Delivery? — Гарантия что сообщение обработается ровно один раз, несмотря на повторы
  • Зачем POST /search если есть GET? — Когда GET-URL слишком длинный для множества фильтров
  • Что делать при Partial Failure POST? — Сервер должен уметь «досоздать» недостающее при повторе с тем же ключом

Красные флаги (НЕ говорить):

  • «POST можно безопасно повторять» — это риск двойных операций
  • «POST никогда не бывает идемпотентным» — можно через Idempotency-Key
  • «POST /search — это нарушение REST» — допустимо когда GET-URL превышает лимит
  • «POST и PUT — одно и то же для обновления» — PUT заменяет целиком, POST создаёт/обрабатывает

Связанные темы:

  • [[Что такое идемпотентность (idempotency)]]
  • [[Какие HTTP методы идемпотентны]]
  • [[Почему GET и DELETE идемпотентны]]
  • [[Какие основные HTTP методы используются в REST]]
  • [[Стоит ли использовать глаголы в URL]]