Computer Science

멱등성(Idempotency)이란, 결제 버튼 두 번 눌러도 안전한 이유

멱등성은 같은 요청을 여러 번 보내도 결과가 같은 성질입니다. 네트워크 실패와 재시도 때문에 필요한 이유, GET·PUT·DELETE와 POST의 차이, 멱등성 키(Idempotency-Key) 구현까지 정리했습니다.

이석우iOS Developer5분 읽기
멱등성(Idempotency)이란, 결제 버튼 두 번 눌러도 안전한 이유 대표 이미지

결제 버튼을 눌렀는데 화면이 한참 멈춰 있으면 불안해집니다. 한 번 더 누르고 싶어지죠.

그런데 두 번 누르면 두 번 결제되는 거 아닐까요?

잘 만든 시스템이라면 안 됩니다. 그리고 그걸 보장하는 성질에 이름이 붙어 있어요.

멱등성(idempotency)입니다.

백엔드 문서, HTTP 명세, 결제 API 가이드에 빠지지 않고 등장하는 개념인데, 정의 자체는 한 줄이면 끝납니다. 어려운 건 “왜 이게 그렇게 중요한가”와 “어떻게 만드는가”예요.

이 글에서 그 두 가지를 채워 보겠습니다.

IDEMPOTENCY 텍스트와 여러 손가락이 누른 엘리베이터 버튼 하나 썸네일
엘리베이터 버튼은 다섯 번 눌러도 5층에 한 번 갑니다

핵심 요약입니다.

  1. 멱등성: 같은 요청을 한 번 보내든 여러 번 보내든 결과가 같은 성질
  2. 중요한 이유: 네트워크는 실패하고, 실패하면 재시도해야 하는데, 재시도가 안전하려면 멱등해야 한다
  3. HTTP에서 GET·PUT·DELETE는 멱등, POST는 멱등이 아니다
  4. 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)예요.

동작 방식은 단순합니다.

  1. 클라이언트가 요청마다 고유 키(보통 UUID)를 만들어 헤더에 실어 보낸다
  2. 서버는 처음 보는 키면 정상 처리하고 키와 함께 응답을 저장해 둔다
  3. 같은 키로 또 오면 처리하지 않고 저장해 둔 응답을 그대로 돌려준다

재시도든 더블클릭이든, 같은 키로 오는 한 서버는 한 번만 처리합니다. 스트라이프와 토스페이먼츠 같은 결제 API들이 실제로 이 헤더를 지원하고, 결제 연동 가이드에서 필수로 다루는 항목이에요.

클라이언트 쪽 디테일 하나. “재시도할 때는 같은 키”를 써야 의미가 있습니다.

재시도마다 키를 새로 만들면 서버는 다른 요청으로 보니까요. 키는 “같은 의도의 요청” 단위로 만들어야 합니다.

같은 키가 찍힌 봉투 둘 중 하나만 처리하고 영수증 한 장을 내는 결제 단말
같은 멱등성 키로 오면 서버는 한 번만 처리하고 저장된 응답을 돌려줍니다

정리

  • 멱등성은 같은 요청을 몇 번 보내도 서버 상태가 같은 성질이다 (f(f(x)) = f(x))
  • 네트워크 실패 시 “됐는지 안 됐는지 모르는” 상황에서, 멱등해야 재시도가 안전해진다
  • HTTP: GET·PUT·DELETE는 멱등, POST는 비멱등, PATCH는 내용에 따라 다르다
  • PUT은 결과 상태를 지정해서 멱등, POST는 행위를 지시해서 비멱등
  • 비멱등한 POST는 멱등성 키(Idempotency-Key)로 멱등하게 만든다 — 같은 키면 한 번만 처리
  • 재시도 시에는 반드시 같은 키를 재사용해야 한다. 키는 “같은 의도의 요청” 단위