Які HTTP методи ідемпотентні
За специфікацією HTTP (RFC 9110) усі методи поділяються на ідемпотентні та неідемпотентні:
🟢 Junior Level
Ідемпотентний метод у протоколі HTTP — це метод, повторне виконання якого з тими самими параметрами залишає стан сервера точно таким самим, як і після першого виконання.
За специфікацією HTTP (RFC 9110) усі методи поділяються на ідемпотентні та неідемпотентні:
Зведена класифікація методів
| HTTP Метод | Безпечний (Safe) | Ідемпотентний (Idempotent) | Семантичне призначення |
|---|---|---|---|
GET |
✅ Так | ✅ Так | Отримання представлення ресурсу (читання). |
HEAD |
✅ Так | ✅ Так | Отримання заголовків без тіла відповіді. |
OPTIONS |
✅ Так | ✅ Так | Запит списку підтримуваних сервером методів та CORS-параметрів. |
TRACE |
✅ Так | ✅ Так | Ехо-тестування шляху запиту (діагностика). |
PUT |
❌ Ні | ✅ Так | Повна заміна наявного ресурсу або створення за фіксованим URI. |
DELETE |
❌ Ні | ✅ Так | Видалення ресурсу. |
POST |
❌ Ні | ❌ Ні | Створення підпорядкованого ресурсу, обробка даних, запуск команд. |
PATCH |
❌ Ні | ❌ Залежить | Часткова зміна ресурсу (за RFC 5789 не гарантується). |
Чому одні методи ідемпотентні, а інші ні
GET /users/42— скільки разів не запитуй, база даних не зміниться.PUT /users/42з тілом{"name": "Ivan"}— перший раз перезапише ім’я на “Ivan”, повторні запити знову запишуть “Ivan”. Стан бази даних той самий.DELETE /users/42— перший раз видалить користувача. Повторні запити переконаються, що користувача немає. Стан (користувач відсутній) не змінився.POST /orders— відправка форми замовлення 3 рази поспіль призведе до створення 3 незалежних замовлень і триразового списання коштів.
🟡 Middle Level
Зв’язок ідемпотентності з кешуванням (RFC 9111)
Ідемпотентність та безпечність безпосередньо визначають роботу проміжних кешувальних серверів (CDN, Cloudflare, NGINX, Squid):
- Кешування Safe методів: Запити
GETтаHEADкешуються за замовчуванням за наявності відповідних заголовків (Cache-Control,ETag,Last-Modified). - Інвалідація кешу неідемпотентними та мутуючими методами:
Згідно з RFC 9111, успішні відповіді на запити
POST,PUT,PATCHтаDELETEзобов’язані інвалідувати (скидати) закешовані представлення для цільового URI (Request-URI), а також для URI, зазначених у заголовкахLocationтаContent-Location.
Оптимістичне блокування через HTTP Preconditions (RFC 9110)
Для запобігання проблемі «втраченого оновлення» (Lost Update Problem) при паралельних PUT та PATCH запитах застосовуються умовні заголовки (Conditional Headers):
PUT /api/v1/products/100 HTTP/1.1
If-Match: "v3-etag-hash"
Content-Type: application/json
{
"price": 199.99
}
- Якщо версія ресурсу в БД збігається з
"v3-etag-hash", сервер оновлює сутність і повертає200 OKз новимETag: "v4-etag-hash". - Якщо інший клієнт встиг оновити сутність раніше, сервер повертає статус
412 Precondition Failed, захищаючи дані від перезапису застарілим станом.
Семантика PATCH: чому специфікація не гарантує ідемпотентність
Метод PATCH (RFC 5789) призначений для часткової модифікації. Ідемпотентність залежить від характеру застосовуваної дельти:
// 1. Ідемпотентний PATCH (JSON Patch RFC 6902)
[
{ "op": "replace", "path": "/email", "value": "new@corp.com" }
]
// 2. НЕІДЕМПОТЕНТНИЙ PATCH (JSON Patch RFC 6902)
[
{ "op": "add", "path": "/roles/-", "value": "ADMIN" }
]
У першому випадку повтор запиту залишає email рівним "new@corp.com". У другому випадку оператор add зі шляхом /- додає елемент у кінець масиву; повторення виклику призведе до масиву ролей ["USER", "ADMIN", "ADMIN", "ADMIN"].
🔴 Senior Level
Ідемпотентність у мікросервісній архітектурі та Service Mesh (Istio / Envoy)
У розподілених інфраструктурах знання ідемпотентності критичне для автоматичної стійкості до збоїв (Retry Policy):
Автоматичний повтор запитів (Envoy VirtualService)
Проксі-сервер (Envoy) у Service Mesh може безпечно повторювати запити тільки для методів з відомою ідемпотентністю. Спроба автоматичного ретраю для POST може призвести до дублювання платежів або замовлень.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service-retry
spec:
hosts:
- order-service
http:
- match:
- uri:
prefix: /api/v1/orders
method:
exact: GET
route:
- destination:
host: order-service
retries:
attempts: 3
perTryTimeout: 500ms
retryOn: "5xx,connect-failure,refused-stream"
Реалізація Resilience4j Retry з фільтрацією за ідемпотентністю в Spring Boot 3
У мікросервісах на Spring Boot міжсервісні виклики через RestClient або WebClient налаштовуються так, щоб повтори виконувалися лише для безпечних та ідемпотентних методів:
package com.example.client;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.time.Duration;
import java.util.Set;
@Component
public class IdempotentRetryInterceptor implements ClientHttpRequestInterceptor {
private static final Set<HttpMethod> IDEMPOTENT_METHODS = Set.of(
HttpMethod.GET,
HttpMethod.HEAD,
HttpMethod.OPTIONS,
HttpMethod.PUT,
HttpMethod.DELETE
);
private final Retry retry;
public IdempotentRetryInterceptor() {
RetryConfig config = RetryConfig.<ClientHttpResponse>custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(200))
.retryExceptions(IOException.class) // Повтор тільки при мережевих збоях (I/O)
.build();
this.retry = Retry.of("idempotentHttpRetry", config);
}
@Override
public ClientHttpResponse intercept(org.springframework.http.HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// Неідемпотентні запити (POST, PATCH) виконуються без автоматичного retry
if (!IDEMPOTENT_METHODS.contains(request.getMethod())) {
return execution.execute(request, body);
}
// Ідемпотентні запити огортаються в Resilience4j Retry
try {
return Retry.decorateCheckedSupplier(retry, () -> execution.execute(request, body)).get();
} catch (Throwable throwable) {
if (throwable instanceof IOException ioException) {
throw ioException;
}
throw new RuntimeException("HTTP retry execution failed", throwable);
}
}
}
Чи допустимі побічні ефекти (Side Effects) в ідемпотентних методів?
Специфікація RFC 9110 (Розділ 9.2.2) прямо роз’яснює фундаментальне питання архітектури:
“The definition of idempotent methods does not prevent an implementation from recording each request, e.g., in an access log, or from taking actions that affect the system outside of the resource state.”
Це означає:
- Запис рядка в Audit Log або
access.logприGETчиDELETEзапиті не порушує ідемпотентність. - Інкремент метрики Prometheus
http_requests_totalне порушує ідемпотентність. - Ідемпотентність оцінюється виключно за цільовим станом ресурсу, яким керує метод.
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
- Ідемпотентні методи (RFC 9110):
GET,HEAD,OPTIONS,TRACE,PUT,DELETE. Повторний виклик залишає ресурс у тому самому стані.- Неідемпотентні методи:
POST(створення/процесинг),PATCH(за RFC 5789 ідемпотентність не гарантується).- Safe ⊂ Idempotent: Усі безпечні методи (
GET,HEAD,OPTIONS) не змінюють стан сервера і є ідемпотентними.PUTтаDELETEзмінюють стан, але повторні виклики детерміновані.- Інфраструктурне значення: Service Mesh (Envoy/Istio) та HTTP-клієнти можуть безпечно застосовувати автоматичні повтори (retries) при збоях мережі лише до ідемпотентних методів.
4 каверзних питання з відповідями
-
Чи є метод PATCH ідемпотентним за стандартом? Відповідь: Ні, за специфікацією RFC 5789 метод
PATCHне гарантує ідемпотентність. Вона залежить від застосовуваних інструкцій: заміна значення (replace) ідемпотентна, а додавання елемента до списку (addу JSON Patch) або інкремент значення при кожному виклику змінюють стан ресурсу. -
Клієнт викликав
DELETE /orders/5і отримав 204 No Content. За секунду викликав знову і отримав 404 Not Found. Чи порушена ідемпотентність? Відповідь: Ні, не порушена. Ідемпотентність гарантує однаковий стан системи (ресурс з ID 5 відсутній у базі даних), а не ідентичність статус-кодів відповідей. -
Що відбувається з кешем при виконанні методів PUT або DELETE? Відповідь: За специфікацією RFC 9111 будь-який успішний запит, що використовує небезпечний метод (
PUT,POST,DELETE,PATCH), зобов’язаний інвалідувати (видалити з кешу) збережену відповідь для цільового URI (Request-URI), а також URI із заголовківLocationтаContent-Location. -
Чому генерація нового
audit_log_idу таблиці аудиту при викликуDELETE /users/1не ламає ідемпотентність? Відповідь: RFC 9110 розділяє стан цільового ресурсу та побічні ефекти інфраструктури (логування, збір метрик, моніторинг). Стан ресурсуUserне змінюється при повторі (він залишається видаленим), тому вимога специфікації дотримана.
Червоні прапорці (чого категорично не можна говорити)
- ❌ «Усі HTTP-методи ідемпотентні, крім POST» (
PATCHза RFC не гарантує ідемпотентність). - ❌ «DELETE не ідемпотентний, тому що коди відповіді 200 і 404 різні» (плутанина між статус-кодом відповіді та станом ресурсу на сервері).
- ❌ «У Service Mesh можна ставити автоматичний retry на будь-які методи, включно з POST» (призведе до дублювання транзакцій та даних).
- ❌ «Ідемпотентність забороняє писати логи у файл при виконанні запиту» (прямо дозволено в RFC 9110 §9.2.2).