"입문서에서는 흔히 'POST는 생성, PUT은 수정'이라고 가르쳐. 하지만 그건 자주 나타나는 결과일 뿐이야. 먼저 봐야 할 규칙은 멱등성이고, 그 규칙을 이해하면 메서드 선택도 자연스럽게 따라와."
모든 메서드를 가르는 세 가지 질문
RFC 9110은 HTTP 메서드의 의미를 설명하는 세 가지 핵심 속성을 정의해. 메서드마다 어떤 속성을 보장하는지가 다르고, 그 조합이 곧 해당 메서드의 계약이 된다. 이 계약은 서버와 클라이언트뿐 아니라 프록시, CDN, 재시도 로직을 포함한 HTTP 생태계 전체가 함께 의존하는 약속이야.
- 안전성 — 클라이언트가 요청한 동작이 서버 상태의 변경을 의도하지 않는다는 뜻이야. 조회는 안전하지만 쓰기는 안전하지 않아. 중간 장치가 미리 가져오거나 자동으로 다시 시도할 수 있는 기반이 되지만, 실제 재시도 여부는 전송 오류와 구현 특성까지 함께 판단해야 해.
- 멱등성 — 같은 요청을 N번 보내도 한 번 보낸 것과 같은 의도된 최종 상태를 남긴다는 뜻이야. 중요한 건 응답이 아니라 상태이야. 예를 들어 삭제 요청은 처음에는 204, 다음에는 404를 돌려줄 수 있지만 최종 상태는 똑같이 '리소스가 없음'이야.
- 캐시 가능성 — 응답을 캐시에 저장해 이후 요청에 재사용할 수 있는지를 나타내. 기본적으로 캐시할 수 있는 메서드가 있는 반면, 명시적인 캐시 지시가 있어야 실용적으로 캐시할 수 있는 메서드도 있어.
메서드 속성표
이 표를 기준으로 보면 "이 설계가 RESTful한가?"라는 논쟁의 상당 부분을 의미론으로 정리할 수 있어.
Method | Safe | Idempotent | Cacheable (기본)
---------|------|------------|----------------
GET | yes | yes | yes
HEAD | yes | yes | yes
OPTIONS | yes | yes | rarely (기술적 yes)
PUT | no | yes | no
DELETE | no | yes | no
POST | no | no | response 가 그러라 해야만
PATCH | no | no (보통) | no
두 가지가 특히 중요해. 첫째, 표준 메서드 가운데 상태를 바꾸면서 멱등성을 보장하는 대표적인 메서드는 PUT과 DELETE야. 같은 쓰기 요청을 재전송해야 할 때 이 차이가 결정적이지. 둘째, POST는 안전성도 멱등성도 보장하지 않는 범용 메서드야. 특정 리소스의 정해진 상태를 교체하기보다 서버에 처리를 맡기는 데 유연하지만, 바로 그 유연성 때문에 무심코 재시도하면 중복 부작용이 생길 수 있어.
운영에서 중요한 이유
이 계약을 지켰을 때와 어겼을 때의 차이는 운영에서 세 가지 모습으로 드러나.
1. 전송 실패 뒤 재시도. 요청은 여러 라우터를 거쳐 서버에 도착했고 서버도 처리를 마쳤지만, 응답 패킷이 돌아오는 길에 사라질 수 있어. 클라이언트는 시간 초과만 보고 서버가 요청을 처리했는지 알 수 없지. 멱등한 메서드라면 같은 요청을 다시 보내도 의도된 최종 상태가 달라지지 않아. 반면 멱등하지 않은 요청을 재시도하면 결제가 두 번 이루어지거나 메시지가 두 번 발송되거나 Council 마무리 작업이 중복 실행될 수 있어.
2. CDN과 프록시의 동작. 캐시는 명시된 정책에 따라 저장한 GET 응답을 적극적으로 재사용해. POST 응답도 명시적인 캐시 지시가 있으면 캐시할 수 있지만, 실제 지원은 제한적인 편이야. 브라우저의 링크 미리 가져오기 역시 보통 GET처럼 안전한 메서드를 대상으로 한다. DELETE를 미리 실행해서는 안 되는 이유도 분명해. 중간 장치가 최적화할 수 있는 범위는 메서드가 약속한 의미론에 의해 정해져.
3. 낙관적 동시성 제어. PUT 같은 수정 요청에 If-Match: "<etag>"를 붙이면 "내가 읽은 뒤 리소스가 바뀌지 않았을 때만 이 요청을 적용하라"는 조건을 전달할 수 있어. 멱등성과 조건부 요청은 서로 다른 속성이지만, 함께 사용하면 재시도와 동시 수정 충돌을 더 예측 가능하게 다룰 수 있지. 자세한 캐시 검증과 조건부 요청은 이 트랙의 네 번째 레슨에서 다뤄.
Idempotency-Key 패턴 — POST에 중복 방지 더하기
POST 자체는 멱등성을 보장하지 않지만, 결제나 계정 생성처럼 안전한 재시도가 필요한 비즈니스 작업은 많아. 이때 클라이언트는 논리적 작업마다 고유한 Idempotency-Key 헤더를 보내고, 서버는 처음 처리한 키와 응답을 일정 기간 저장해. 같은 키가 다시 오면 작업을 반복하지 않고 저장해 둔 결과를 반환하는 방식이야. Stripe가 널리 알린 패턴으로, 중복 부작용을 막아야 하는 POST에서 사실상 표준처럼 쓰여.
cwkPippa의 실제 사례
POST /api/chat은 멱등하지 않아. 같은 메시지를 두 번 보내면 어시스턴트 응답도 두 개 생성되고 Claude API 토큰 비용도 두 번 발생해. 프런트엔드는 중복 입력을 막고 전송 시 UUID를 붙여 위험을 줄이지만, 전송 계층의 POST에는 아직 Idempotency-Key가 없어. 중복 전송이 실제 운영 문제로 드러나면 우선 보강할 지점이야. 반면 backend/routes/의 PUT과 DELETE는 같은 요청을 반복해도 의도된 최종 상태가 같도록 설계되어 있어. 이 비대칭은 각 작업의 의미가 다르기 때문에 생긴 의도적인 선택이야.