HTTP 메서드에는 두 가지 중요한 성질이 있습니다: 안전성(safe)과 멱등성(idempotent). 이 개념은 단순한 이론이 아니라, 네트워크가 실패했을 때 요청을 재시도해도 되는지를 결정하는 실전 기준입니다.
안전한 메서드 vs 멱등한 메서드
안전한(safe) 메서드는 서버의 상태를 변경하지 않는 읽기 전용 작업입니다. GET, HEAD, OPTIONS가 여기 속합니다. 안전한 요청은 몇 번을 보내도 부작용이 없으므로 프리페치·캐싱·크롤링이 자유롭습니다.
멱등한(idempotent) 메서드는 '같은 요청을 여러 번 보내도 결과 상태가 한 번 보낸 것과 같은' 성질입니다. 응답 자체가 매번 똑같다는 뜻이 아니라, 서버에 남는 최종 상태가 같다는 뜻입니다. 모든 안전한 메서드는 자동으로 멱등하며, 여기에 PUT과 DELETE가 추가됩니다.
GET,HEAD,OPTIONS: 안전 + 멱등 (상태 변경 없음).PUT: 멱등.PUT은 리소스를 특정 표현으로 '통째로 대체'하므로, 같은PUT을 두 번 보내도 최종 상태는 동일합니다.DELETE: 멱등. 처음엔 삭제되고 그다음엔 '이미 없음' 상태로 최종 상태가 같습니다(두 번째가 404를 줘도 상태는 동일).POST: 안전하지도 멱등하지도 않음. 보낼 때마다 새 리소스가 생기는 게 일반적.PATCH: 명세상 멱등이 보장되지 않음(구현에 따라 다름).
왜 재시도에 중요한가
네트워크는 언제든 끊깁니다. 요청은 서버에 도달했는데 응답이 오는 길에 유실되면, 클라이언트는 '성공했는지 실패했는지' 알 수 없습니다. 이때 멱등한 메서드라면 그냥 다시 보내도 안전합니다 — PUT이나 DELETE는 중복 실행돼도 최종 상태가 같으니까요. 그래서 HTTP 클라이언트·프록시·로드밸런서는 관례적으로 멱등 메서드만 자동 재시도합니다. 반면 POST를 무턱대고 재시도하면 결제가 두 번 되거나 주문이 중복 생성될 수 있습니다.
멱등성 키로 POST를 안전하게
그렇다면 본질적으로 멱등하지 않은 POST(결제·주문 생성 등)를 어떻게 안전하게 재시도할까요? 해답은 멱등성 키(idempotency key)입니다. 클라이언트가 요청마다 고유한 키를 Idempotency-Key 헤더에 실어 보내면, 서버는 그 키를 저장해 두고 같은 키의 첫 요청만 실제로 실행한 뒤, 이후 동일 키 요청에는 저장해 둔 원래 응답을 그대로 재생(replay)합니다.
POST /v1/orders HTTP/1.1
Idempotency-Key: 8e2f4c9a-1b7d-4e6f-a0c3-9d2f1a7b3c4a
Content-Type: application/json
{"sku":"BOOK-01","qty":1}
서버 구현의 핵심은 (1) 키를 원자적으로 기록해 동시 요청 경합을 막고, (2) 원래 요청 본문과 응답을 함께 저장해 재시도 시 동일하게 돌려주며, (3) 키에 적절한 만료를 두는 것입니다. 이 패턴 덕분에 클라이언트는 응답을 못 받았을 때 마음 놓고 재시도할 수 있습니다.
PATCH와 DELETE를 안전하게 설계하기
PATCH는 부분 수정이라 멱등이 보장되지 않습니다. 예를 들어 { "balance": "+100" }처럼 증분 연산으로 설계하면 두 번 적용 시 결과가 달라져 멱등이 깨집니다. 반면 { "balance": 500 }처럼 절대값 지정으로 설계하면 여러 번 적용해도 결과가 같아 멱등해집니다. 동시 수정 충돌을 막으려면 If-Match에 ETag를 실어 낙관적 잠금을 걸고, 버전이 어긋나면 서버가 412 Precondition Failed를 반환하게 하세요.
DELETE는 이미 멱등이지만, 두 번째 삭제 요청에 404를 줄지 204를 줄지 팀 규칙을 정해 두면 클라이언트가 혼란스럽지 않습니다. 요약하면, 안전성·멱등성은 단순한 분류가 아니라 장애에 강한(resilient) API를 만드는 설계 원칙입니다.