결제 버튼을 눌렀는데 화면이 한참 멈춰 있으면 불안해집니다. 한 번 더 누르고 싶어지죠.
그런데 두 번 누르면 두 번 결제되는 거 아닐까요?
잘 만든 시스템이라면 안 됩니다. 그리고 그걸 보장하는 성질에 이름이 붙어 있어요.
멱등성(idempotency)입니다.
백엔드 문서, HTTP 명세, 결제 API 가이드에 빠지지 않고 등장하는 개념인데, 정의 자체는 한 줄이면 끝납니다. 어려운 건 “왜 이게 그렇게 중요한가”와 “어떻게 만드는가”예요.
이 글에서 그 두 가지를 채워 보겠습니다.
핵심 요약입니다.
- 멱등성: 같은 요청을 한 번 보내든 여러 번 보내든 결과가 같은 성질
- 중요한 이유: 네트워크는 실패하고, 실패하면 재시도해야 하는데, 재시도가 안전하려면 멱등해야 한다
- HTTP에서 GET·PUT·DELETE는 멱등, POST는 멱등이 아니다
- POST를 안전하게 만드는 실무 장치가 멱등성 키(Idempotency-Key)
정의: 여러 번 해도 한 번 한 것과 같다
멱등성은 수학에서 온 말입니다. 어떤 연산 f가 f(f(x)) = f(x)를 만족하면 멱등이라고 해요.
절댓값 함수가 좋은 예입니다. |−5| = 5이고 |5| = 5죠.
한 번 적용하나 백 번 적용하나 결과가 같습니다.
API에 적용하면 이렇게 됩니다.
같은 요청을 1번 보내든 N번 보내든, 서버의 상태가 같다.
주의할 점: 응답이 같아야 한다는 게 아니라 서버 상태가 같아야 한다는 겁니다. 두 번째 DELETE 요청이 404를 돌려줘도, “그 리소스가 없다”는 서버 상태는 동일하니 멱등이에요.
일상 비유로는 엘리베이터 버튼입니다. 5층 버튼을 다섯 번 눌러도 엘리베이터는 5층에 한 번 갑니다.
반대로 “5층 더 올라가기” 버튼이었다면 누를 때마다 결과가 달라지겠죠. 앞쪽이 멱등, 뒤쪽이 비멱등입니다.
왜 중요한가: 네트워크는 반드시 실패하니까
멱등성이 중요한 이유는 단 하나의 시나리오로 설명됩니다.
클라이언트가 결제 요청을 보냈습니다. 서버는 결제를 처리했어요.
그런데 응답이 돌아오는 길에 네트워크가 끊겼습니다.
클라이언트 입장에서는 알 방법이 없습니다. 요청이 서버에 도착하기 전에 죽었는지(결제 안 됨), 처리 후 응답만 유실됐는지(결제 됨).
타임아웃이라는 결과는 두 경우에 똑같이 생기거든요.
여기서 선택지는 둘뿐입니다. 재시도를 포기하거나(결제가 안 됐을 수도 있는데), 재시도하거나(이미 됐다면 중복 결제인데). 둘 다 끔찍하죠.
멱등성은 이 딜레마를 없앱니다. 요청이 멱등하다면 “모르겠으면 다시 보내”가 항상 안전한 전략이 돼요.
자동 재시도, 메시지 큐의 최소 1회 전달(at-least-once), 클라이언트의 새로고침 연타까지 전부 두려울 게 없어집니다.
분산 시스템 설계 문서마다 “재시도는 멱등한 연산에만 걸어라”가 반복되는 이유입니다.
HTTP 메서드로 보는 멱등성
HTTP 명세(RFC 9110)는 메서드별 멱등성을 명시하고 있습니다.
| 메서드 | 멱등? | 이유 |
|---|---|---|
| GET | O | 조회는 상태를 안 바꾼다 |
| PUT | O | “이 값으로 교체”는 몇 번 해도 같은 값 |
| DELETE | O | “지워라”는 몇 번 해도 지워진 상태 |
| POST | X | “새로 만들어라”는 부를 때마다 하나씩 는다 |
| PATCH | 조건부 | “이 필드를 X로”는 멱등, “1 더해라”는 비멱등 |
PUT과 POST의 대비가 핵심입니다. PUT은 “결과 상태”를 지정하니 멱등하고 POST는 “행위”를 지시하니 비멱등이에요.
PATCH는 내용에 따라 갈립니다.
브라우저가 뒤로 가기로 POST 페이지에 돌아갈 때 “양식을 다시 제출하시겠습니까?“라고 겁을 주는 것도, POST 재전송이 안전하지 않다는 걸 브라우저가 알기 때문입니다.
멱등성 키라는 실무 장치
그런데 회원 가입, 주문 생성, 결제는 본질적으로 POST입니다. 비멱등한 작업을 안 할 수는 없어요.
그래서 비멱등한 요청을 멱등하게 만드는 장치가 나왔습니다. 멱등성 키(Idempotency-Key)예요.
동작 방식은 단순합니다.
- 클라이언트가 요청마다 고유 키(보통 UUID)를 만들어 헤더에 실어 보낸다
- 서버는 처음 보는 키면 정상 처리하고 키와 함께 응답을 저장해 둔다
- 같은 키로 또 오면 처리하지 않고 저장해 둔 응답을 그대로 돌려준다
재시도든 더블클릭이든, 같은 키로 오는 한 서버는 한 번만 처리합니다. 스트라이프와 토스페이먼츠 같은 결제 API들이 실제로 이 헤더를 지원하고, 결제 연동 가이드에서 필수로 다루는 항목이에요.
클라이언트 쪽 디테일 하나. “재시도할 때는 같은 키”를 써야 의미가 있습니다.
재시도마다 키를 새로 만들면 서버는 다른 요청으로 보니까요. 키는 “같은 의도의 요청” 단위로 만들어야 합니다.
정리
- 멱등성은 같은 요청을 몇 번 보내도 서버 상태가 같은 성질이다 (f(f(x)) = f(x))
- 네트워크 실패 시 “됐는지 안 됐는지 모르는” 상황에서, 멱등해야 재시도가 안전해진다
- HTTP: GET·PUT·DELETE는 멱등, POST는 비멱등, PATCH는 내용에 따라 다르다
- PUT은 결과 상태를 지정해서 멱등, POST는 행위를 지시해서 비멱등
- 비멱등한 POST는 멱등성 키(Idempotency-Key)로 멱등하게 만든다 — 같은 키면 한 번만 처리
- 재시도 시에는 반드시 같은 키를 재사용해야 한다. 키는 “같은 의도의 요청” 단위
