"모든 tutorial 이 'POST 가 create, PUT 이 update' 라고 가르쳐. 그건 결과야. 규칙은 idempotency — 규칙을 한 번 보면, verb 매핑이 스스로 정해져."
모든 method 를 정의하는 세 yes/no 질문
RFC 9110 은 세 semantic 속성을 정의해. 모든 method 가 각 속성을 가지거나 안 가져. 조합이 method 의 계약 — 전체 HTTP 생태계 (client, proxy, CDN, retry logic) 와의.
- Safe — Request 가 server state 에 관찰 가능한 변화 안 만듦. 읽기는 safe; 쓰기는 아님. Safe request 는 자동 retry, prefetch, 추측 실행 가능.
- Idempotent — 같은 request N번 보내도 한 번 보낸 것과 같은 state 남음. (주의: state, response 가 아님. DELETE 는 첫 번째 204, 두 번째 404 — 다른 response, 같은 최종 state.)
- Cacheable — Response 가 intermediary 에 저장되고 재사용 허용. 기본으로 cacheable 한 method 도 있고; 명시 header 있어야만 한 것도 있음.
Method 매트릭스
한 번 외우면 "이게 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
두 가지 주목. 첫째: state 바꾸면서 idempotent 인 method 는 PUT 과 DELETE 뿐. 그게 "쓰기에 safe retry" 의 전체 모양. 둘째: POST 가 wildcard — safe 도 idempotent 도 아님 — 이 endpoint 가 뭐 하든 "그 뭐 해줘" 만능이라서. 그 열려있음이 POST 를 유용하게 만들고 순진한 POST client 를 위험하게 만들어.
Production 이 신경 쓰는 이유
제대로 (혹은 잘못) 하면 발생하는 현실 결과 셋:
1. Transport retry. Request 가 라우터 10개 거쳐 server 도달. Server 처리. Response 패킷이 돌아오는 길에 떨어짐. Client timeout 됐는데 모름: server 가 봤어? Idempotent method 면 client 가 안전하게 retry 가능 — 최악의 경우 server 가 다른 최종 state 없이 다시 처리. Not-idempotent method 면 retry 위험: payment, message-send, council-finalize. 잘못된 retry 가 카드 두 번 청구.
2. CDN 과 proxy 행동. Cache 가 GET 의 cached response 를 적극적으로 서빙. POST 는 명시 opt-in 없으면 절대 안 캐시 (해도 대부분 안 함). 가끔 link 를 추측적으로 prefetch 하는데 GET 만 — DELETE 를 "미리" fetch 하면 재앙. Protocol 이 cache 가 안전히 이걸 하게 해주는 이유는 method 가 올바른 invariant 약속하니까.
3. Optimistic locking. PUT 이 idempotent 라서 If-Match: "<etag>" 가능 — "내가 읽은 이후 resource 가 update 안 됐을 때만 이 PUT 적용." Idempotency 없으면 이 춤이 일관성 잃어. Track 2 lesson 4 가 cache 계약 다뤄; foundation 은 여기.
Idempotency-Key 패턴 (POST 의 우회)
POST 는 idempotent 아님 — 근데 비즈니스 operation 은 자주 그래야 함. 널리 채택된 해결책: client 가 각 논리적 operation 마다 unique Idempotency-Key header 생성. Server 가 본 key 저장하고 같은 key 의 반복에 같은 response 돌려줌. Stripe 가 대중화했고; 지금은 안전하게 retry 하고 싶은 모든 POST (payment, 계정 생성, side effect 있는 거 뭐든) 의 표준.
cwkPippa 의 현실
POST /api/chat 은 진짜로 idempotent 아냐 — 같은 메시지 두 번 보내면 assistant response 가 두 개 생성 (Claude API 토큰 두 번 비용). Frontend 가 debouncing 과 send-button-click 당 UUID 로 완화하지만, wire-level POST 엔 아직 Idempotency-Key 없음. 알려진 gap; production 에서 무는 날이 추가하는 날. 한편 backend/routes/ 의 모든 PUT/DELETE 는 retry 안전 — idempotent 설계라서. 비대칭은 의도적.