Идемпотентность
Идемпотентность (Idempotency) — повторяемость без изменения результата
TL;DR
Идемпотентность — свойство операции: повторное выполнение с теми же входными данными не меняет итоговый результат системы. Критично для платежей, заказов и API с повторными отправками.
Краткое определение
Идемпотентность — свойство операции, при котором повторное выполнение с теми же входными данными не меняет финальное состояние системы после первого успешного выполнения.
Оригинал и перевод
- Язык: английский
- Оригинал: Idempotency (от лат. idem — тот же + potens — сильный)
- Буквальный перевод: одинаковый результат при повторении
- Русские аналоги: идемпотентность, неизменность результата
Синонимы и варианты написания
- Идемпотентная операция
- Идемпотентный метод
- Idempotent operation
- Повторяемость без побочных эффектов
Где используется
- HTTP API: методы GET, PUT, DELETE (но не POST по умолчанию)
- Платёжные системы: защита от двойных списаний
- Очереди сообщений: повторная обработка без дублирования
- Базы данных: UPSERT операции (INSERT ... ON CONFLICT UPDATE)
- Распределённые системы: ретраи при сетевых сбоях
Когда это важно
Идемпотентность критична в следующих сценариях:
- Повторная отправка запросов при таймаутах, обрывах соединения
- Ретраи в очередях (RabbitMQ, Kafka) — сообщение может доставляться несколько раз
- Платежи и заказы — нельзя создать дубль транзакции
- Синхронизация данных — повторный запрос не должен ломать состояние
Как это работает (принцип)
Пример из математики
f(x) = |x| (модуль числа) f(5) = 5 f(f(5)) = f(5) = 5 ← идемпотентно f(x) = x + 1 f(5) = 6 f(f(5)) = f(6) = 7 ← НЕ идемпотентно
Пример из HTTP
# PUT запрос (идемпотентный)
PUT /users/123/status
{"status": "paid"}
# Первый вызов: статус изменён на "paid"
# Второй вызов: статус остаётся "paid" (результат тот же)
# Третий вызов: статус всё ещё "paid" ✅
# POST запрос (НЕ идемпотентный по умолчанию)
POST /payments
{"amount": 1000}
# Первый вызов: платёж создан #1
# Второй вызов: платёж создан #2 ← дубль! ❌
# Третий вызов: платёж создан #3 ← ещё один дубль!
Реализация идемпотентности для API
Pattern: Idempotency Key
Клиент генерирует уникальный ключ для каждой операции и передаёт в заголовке:
POST /payments
Idempotency-Key: pay_abc123xyz789
Content-Type: application/json
{"amount": 1000, "currency": "RUB"}
Сервер обрабатывает:
// Псевдокод обработчика
function processPayment($request) {
$key = $request->header('Idempotency-Key');
// Проверяем, есть ли уже результат для этого ключа
$cachedResult = $redis->get("idempotency:{$key}");
if ($cachedResult) {
return $cachedResult; // Возвращаем кэшированный результат
}
// Блокируем обработку этого ключа (защита от race condition)
$lock = $redis->set("lock:{$key}", "processing", ['NX', 'EX' => 30]);
if (!$lock) {
wait_and_retry();
}
// Выполняем платёж
$result = executePayment($request->body);
// Кэшируем результат (24 часа)
$redis->setex("idempotency:{$key}", 86400, json_encode($result));
return $result;
}
HTTP методы и идемпотентность
| Метод | Идемпотентный? | Почему |
|---|---|---|
| GET | ✅ Да | Только чтение, не меняет состояние |
| PUT | ✅ Да | Замена ресурса целиком, результат одинаковый |
| DELETE | ✅ Да | Удаление: первый раз удаляет, последующие — ничего не делают |
| PATCH | ⚠️ Зависит | Если применяется к состоянию — может быть неидемпотентным |
| POST | ❌ Нет | Создание нового ресурса, каждый вызов = новый объект |
Типичные ошибки при реализации
- ❌ Нет Idempotency-Key — клиент не может безопасно повторить запрос
- ❌ Кэш результата слишком короткий — при ретрае ключ уже удалён
- ❌ Генерация ключа на сервере — клиент должен сам генерировать ключ
- ❌ Идемпотентность только по URL — разные тела запроса = разные операции
- ❌ Отсутствие блокировки — race condition при параллельных запросах
Аналоги и связанные термины
- Retry — механизм повторных попыток (требует идемпотентности)
- Exactly-once semantics — семантика «ровно один раз» (идеал, трудно достижим)
- At-least-once semantics — «как минимум один раз» (требует идемпотентности)
- Deduplication — устранение дубликатов
- Optimistic locking — оптимистическая блокировка через версии
Смотри также (статьи на сайте)
- PHP 8.5: pipe-оператор для пайплайна данных — обработка запросов с идемпотентностью
- Безопасный AJAX endpoint в Bitrix — защита от повторных отправок
- Идемпотентный агент с блокировкой — защита от параллельного запуска
Смотри также (сниппеты)
- Идемпотентный агент с lock-файлом — защита от параллельного выполнения
- Безопасное обновление заказа без рекурсии — идемпотентность в Bitrix Sale
Смотри также (термины)
- Rate limiting — ограничение частоты запросов (дополняет идемпотентность)
- Backpressure — обратное давление в потоках
- ACID — атомарность транзакций (связано с надёжностью)
Мини-FAQ
Идемпотентность и кэш — одно и то же?
Ответ: Нет. Идемпотентность — про одинаковый результат при повторении операции. Кэш — про хранение ответа для ускорения. Но кэш часто используют для реализации идемпотентности (кэширование результата по ключу).
POST можно сделать идемпотентным?
Ответ: Да, через Idempotency-Key. Но по семантике REST POST создаёт новый ресурс, поэтому лучше использовать PUT для идемпотентного обновления.
Обязательно ли хранить результат 24 часа?
Ответ: Зависит от сценария. Для платежей — желательно долго (клиент может повторить через часы). Для некритичных операций — достаточно 5-15 минут.
Что делать, если клиент не передаёт Idempotency-Key?
Ответ: Для критичных операций (платежи) — требовать ключ обязательно (возвращать 400 Bad Request). Для некритичных — можно генерировать временный ключ на основе хэша тела запроса + timestamp.
Идемпотентность замедляет систему?
Ответ: Минимально. Проверка кэша и блокировка занимают миллисекунды. Это необходимая плата за надёжность в distributed systems.