멱등성

멱등성(Idempotent)이란 동일한 요청을 한 번 보내든 여러 번 보내든 서버의 상태가 동일하게 유지되는 성질입니다.

핵심 개념

[멱등하다]
같은 요청을 1번 보내든 100번 보내든 → 서버 상태가 동일

[멱등하지 않다]
같은 요청을 보낼 때마다 → 서버 상태가 계속 변함

주의할 점은 응답이 동일한 것이 아니라 서버의 "상태"가 동일한 것입니다.

[DELETE 예시 — 멱등 O]
DELETE /members/1 → 200 OK (삭제됨)
DELETE /members/1 → 404 Not Found (이미 없음)

→ 응답 코드는 다르지만, 서버 상태는 동일 (members/1이 없는 상태)
→ 멱등하다 O

HTTP Method별 멱등성

Method멱등이유
GETO조회만 하므로 서버 상태 변경 없음
PUTO같은 데이터로 덮어쓰기 → 몇 번 해도 결과 동일
DELETEO이미 삭제된 리소스를 다시 삭제해도 상태 동일
HEADOGET과 동일하나 본문 없음 → 서버 상태 변경 없음
OPTIONSO서버 정보 확인만 → 변경 없음
POSTX호출할 때마다 새 리소스가 생성됨
PATCHX구현에 따라 결과가 달라질 수 있음

각 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)
→ 재시도 시 부작용 발생 가능
→ 멱등키, 중복 체크 등 별도 방어 로직 필요

면접 포인트

CSLOG에서 보기