멱등성
멱등성(Idempotent)이란 동일한 요청을 한 번 보내든 여러 번 보내든 서버의 상태가 동일하게 유지되는 성질입니다.
핵심 개념
[멱등하다]
같은 요청을 1번 보내든 100번 보내든 → 서버 상태가 동일
[멱등하지 않다]
같은 요청을 보낼 때마다 → 서버 상태가 계속 변함
주의할 점은 응답이 동일한 것이 아니라 서버의 "상태"가 동일한 것입니다.
[DELETE 예시 — 멱등 O]
DELETE /members/1 → 200 OK (삭제됨)
DELETE /members/1 → 404 Not Found (이미 없음)
→ 응답 코드는 다르지만, 서버 상태는 동일 (members/1이 없는 상태)
→ 멱등하다 O
HTTP Method별 멱등성
| Method | 멱등 | 이유 |
|---|---|---|
| GET | O | 조회만 하므로 서버 상태 변경 없음 |
| PUT | O | 같은 데이터로 덮어쓰기 → 몇 번 해도 결과 동일 |
| DELETE | O | 이미 삭제된 리소스를 다시 삭제해도 상태 동일 |
| HEAD | O | GET과 동일하나 본문 없음 → 서버 상태 변경 없음 |
| OPTIONS | O | 서버 정보 확인만 → 변경 없음 |
| POST | X | 호출할 때마다 새 리소스가 생성됨 |
| PATCH | X | 구현에 따라 결과가 달라질 수 있음 |
각 Method 예시
[GET — 멱등 O]
GET /members/1 → {id: 1, name: "홍길동"}
GET /members/1 → {id: 1, name: "홍길동"}
→ 몇 번을 조회해도 서버 상태 변화 없음
[PUT — 멱등 O]
PUT /members/1 {name: "김철수"} → {id: 1, name: "김철수"}
PUT /members/1 {name: "김철수"} → {id: 1, name: "김철수"}
→ 같은 데이터로 전체 교체 → 몇 번 해도 결과 동일
[DELETE — 멱등 O]
DELETE /members/1 → 삭제됨 (200)
DELETE /members/1 → 이미 없음 (404)
→ "members/1이 없는 상태"는 동일
[POST — 멱등 X]
POST /members {name: "홍길동"} → 생성됨 (id: 1)
POST /members {name: "홍길동"} → 생성됨 (id: 2)
POST /members {name: "홍길동"} → 생성됨 (id: 3)
→ 호출할 때마다 새로운 리소스가 생성됨
PATCH가 멱등하지 않은 이유
[멱등한 PATCH — 절대값 수정]
PATCH /members/1 {name: "김철수"}
PATCH /members/1 {name: "김철수"}
→ 결과 동일 → 이 경우는 멱등
[멱등하지 않은 PATCH — 상대값 수정]
PATCH /accounts/1 {operation: "add", amount: 1000}
→ 잔액: 10,000 → 11,000
PATCH /accounts/1 {operation: "add", amount: 1000}
→ 잔액: 11,000 → 12,000
→ 호출할 때마다 결과가 달라짐 → 멱등하지 않음
→ 구현 방식에 따라 달라지므로 HTTP 스펙에서 PATCH를 멱등으로 보장하지 않음
멱등성이 왜 중요한가 — 네트워크 재시도
[네트워크 장애 시나리오]
클라이언트 → 서버: 요청 전송
서버: 처리 완료
서버 → 클라이언트: 응답 전송 중 네트워크 끊김
클라이언트: 응답 못 받음 → "실패했나?" → 재시도
[멱등한 요청 — 안전하게 재시도 가능]
DELETE /members/1 → (응답 유실) → 재시도 DELETE /members/1
→ 이미 삭제된 상태 → 서버 상태 동일 → 문제 없음 O
[멱등하지 않은 요청 — 재시도 위험]
POST /orders {상품: A, 수량: 1} → (응답 유실) → 재시도 POST /orders
→ 주문이 2건 생성됨 → 중복 주문 X
// POST 중복 방지 — 멱등키(Idempotency Key) 패턴
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(
@RequestHeader("Idempotency-Key") String idempotencyKey,
@RequestBody OrderRequest request) {
// 이미 처리된 요청인지 확인
Optional<OrderResponse> existing = orderService.findByIdempotencyKey(idempotencyKey);
if (existing.isPresent()) {
return ResponseEntity.ok(existing.get()); // 이전 결과 반환
}
// 새 주문 생성
OrderResponse order = orderService.create(request, idempotencyKey);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
// 클라이언트가 고유한 멱등키를 포함하여 요청
POST /orders HTTP/1.1
Idempotency-Key: 550e8400-e29b-41d4-a716 ← 고유 키
Content-Type: application/json
{"productId": 1, "quantity": 1}
// 네트워크 장애로 재시도해도
POST /orders HTTP/1.1
Idempotency-Key: 550e8400-e29b-41d4-a716 ← 같은 키
Content-Type: application/json
{"productId": 1, "quantity": 1}
→ 서버: 이미 처리된 키 → 이전 결과 반환 → 중복 생성 방지 O
멱등성 정리
멱등한 메서드 (GET, PUT, DELETE)
→ 네트워크 장애 시 안전하게 재시도 가능
→ 로드밸런서, 프록시가 자동 재시도 가능
멱등하지 않은 메서드 (POST, PATCH)
→ 재시도 시 부작용 발생 가능
→ 멱등키, 중복 체크 등 별도 방어 로직 필요
면접 포인트
- 멱등성은 "응답이 같다"가 아니라 "서버 상태가 같다"는 의미이며, DELETE는 응답 코드가 달라져도 멱등합니다.
- 멱등성의 실무적 가치는 네트워크 장애 시 재시도 안전성이며, 멱등한 요청은 안전하게 재시도할 수 있지만 POST는 중복 생성 위험이 있어 멱등키 패턴 등의 별도 방어가 필요합니다.
- PATCH는 구현 방식에 따라 멱등할 수도, 아닐 수도 있으므로 HTTP 스펙에서 멱등을 보장하지 않습니다.