Что такое идемпотентность

Операция идемпотентна, если её результат не меняется от количества выполнений. Стандартный пример: присвоение значения переменной (x = 5) идемпотентно — можно выполнить сто раз, результат тот же. Инкремент (x += 1) — нет.

В контексте API и распределённых систем идемпотентность — это гарантия безопасного повтора при сбоях:

# Идемпотентный запрос — PUT
PUT /cart/items/SKU-1234
{ "quantity": 2 }
→ Повтор даст тот же результат: quantity = 2

# Неидемпотентный запрос — POST
POST /cart/items
{ "sku": "SKU-1234", "quantity": 2 }
→ Повтор создаст вторую позицию: quantity = 4

Почему это важно для e-commerce

В интернет-магазинах каждый запрос проходит через цепочку: браузер → CDN → API-шлюз → бэкенд → база данных. В этой цепочке возникают таймауты, обрывы соединения и перезапросы. Без идемпотентности:

  • Дублирование заказов: клиент нажал «Купить», ответ не пришёл, браузер повторил запрос — создался второй заказ.
  • Двойное списание: платёжный шлюз получил retry и провёл транзакцию дважды.
  • Искажение аналитики: событие purchase или add_to_cart засчиталось несколько раз.

Idempotency Key: практическая реализация

Стандартный паттерн — клиент генерирует UUID для каждой операции и передаёт его в заголовке:

POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{ "cart_id": "abc123", "payment_method": "card_xxx" }

Сервер сохраняет (idempotency_key → response) с TTL, обычно 24–48 часов. При повторном запросе с тем же ключом возвращается кэшированный ответ без повторного выполнения операции.

Важно: TTL idempotency key должен быть достаточным, чтобы покрыть типичное время retry — обычно несколько минут. Но не вечным — иначе разрастётся хранилище.

Идемпотентность в платформах персонализации

Для систем рекомендаций и A/B тестирования важны два аспекта:

Сценарий Риск без идемпотентности Решение
Event API (view, purchase) Дублирование увеличивает вес товара в профиле Дедупликация по event_id + временное окно
Конверсионное событие в A/B тесте Одна транзакция засчитана дважды в метрике Unique constraint по (test_id, user_id, order_id)
Обновление профиля пользователя Лишние данные искажают сегментацию PUT вместо POST для обновлений

Типичные ошибки интеграции

  • Использовать POST вместо PUT для обновлений сущностей — делает операцию принципиально неидемпотентной
  • Не передавать idempotency key при платёжных операциях
  • Обрабатывать retry в коде клиента без дедупликации на сервере
  • Не предусматривать event_id в схеме событий трекинга — потом добавить без миграции сложно