uniflow
KO / EN
Dev·개념·2026-06-30

멱등성(idempotency)이 뭐길래 — 결제·주문 중복을 막는 법

결제 버튼을 두 번 누르면 두 번 결제될까요? 이 흔한 사고를 구조로 막는 개념이 멱등성입니다. 멱등성이 무엇인지, HTTP 메서드로 어떻게 갈리는지, 결제·주문에서 멱등성 키로 중복을 막는 법과 기획자가 정해야 할 항목까지 정리했습니다.

결제 버튼을 두 번 누르면 두 번 결제될까

온라인 결제를 누르고 화면이 멈칫하면, 누구나 한 번쯤 버튼을 다시 누릅니다. 그런데 그 순간 정말 두 번 결제가 될까요? 잘 만든 시스템은 두 번 눌러도 결제가 한 번만 됩니다. 그걸 보장하는 핵심 개념이 바로 멱등성(idempotency)입니다.

이 개념은 개발자만의 용어처럼 들리지만, 사실 기획자·PM·운영 담당자가 더 자주 마주치는 문제입니다. "중복 결제 환불 처리", "주문이 두 건 들어왔어요", "포인트가 두 번 적립됐어요" 같은 사고가 전부 멱등성이 빠진 자리에서 생깁니다. 이 글에서는 멱등성이 무엇인지, 왜 결제·주문에서 특히 중요한지, 그리고 실무에서 무엇을 정해야 하는지를 정리합니다.

멱등성이란 — 몇 번 해도 결과가 같은 성질

멱등성은 한 마디로 **"같은 요청을 한 번 보내든 여러 번 보내든, 결과가 한 번 보낸 것과 똑같은 성질"**입니다. 수학에서 온 말이지만 정의는 직관적입니다. 전등 스위치를 예로 들면 이해가 빠릅니다.

  • **"불을 꺼라"**는 멱등합니다. 이미 꺼져 있을 때 또 꺼도 결과는 "꺼짐"으로 같습니다. 열 번 눌러도 결과는 하나입니다.
  • **"불 상태를 토글해라"**는 멱등하지 않습니다. 누를 때마다 켜졌다 꺼졌다 결과가 바뀝니다.

결제로 옮기면, "이 주문을 결제 완료 상태로 만들어라"는 멱등하게 설계할 수 있지만, "결제를 한 건 새로 추가해라"는 그대로 두면 멱등하지 않습니다. 두 번 보내면 두 건이 쌓이니까요.

멱등한 연산 vs 멱등하지 않은 연산

핵심은 "상태를 특정 값으로 맞추는가" 대 **"무언가를 새로 더하는가"**의 차이입니다. 값을 맞추는 연산(설정·교체·삭제)은 자연히 멱등하고, 더하는 연산(생성·누적)은 그대로 두면 멱등하지 않습니다. 그래서 위험한 쪽은 항상 "새로 더하는" 결제·주문·적립 같은 동작입니다.

HTTP 메서드로 보는 멱등성

웹 API는 이 성질을 메서드 차원에서 이미 구분해 둡니다. 표준(RFC 9110)이 정의한 분류는 다음과 같습니다.

메서드멱등성의미
GETO조회 — 몇 번 읽어도 데이터가 변하지 않음
PUTO전체 교체 — 같은 값으로 덮어쓰면 결과 동일
DELETEO삭제 — 두 번 지워도 "없음" 상태로 같음
POSTX생성 — 두 번 보내면 두 건 생성(중복 결제!)
PATCHX부분 수정 — 누적형이면 결과가 달라짐

문제는 결제·주문 같은 "생성" 동작이 대부분 POST라는 점입니다. POST는 표준상 멱등하지 않습니다. 즉 네트워크가 끊겨 클라이언트가 재시도하거나, 사용자가 버튼을 두 번 누르면 그대로 두 건이 생깁니다. 그래서 별도 장치가 필요합니다.

결제·주문(POST)은 어떻게 막나 — 멱등성 키

비멱등한 POST를 멱등하게 만드는 표준 방법이 멱등성 키(Idempotency-Key)입니다. 토스페이먼츠·Stripe 같은 결제사 API가 공통으로 제공하는 기능이고, IETF가 표준 헤더로 초안을 진행 중일 만큼 정착된 패턴입니다.

동작 원리 — 키 하나로 중복을 흡수한다

동작은 세 단계로 단순합니다.

  1. 클라이언트가 요청에 고유한 키(보통 UUID)를 함께 보냅니다.
  2. 서버는 그 키로 처리 결과를 저장합니다. 처음 보는 키면 정상 처리합니다.
  3. 같은 키가 다시 들어오면 다시 처리하지 않고, 저장해 둔 원래 결과를 그대로 돌려줍니다.

요청 모습은 대략 이렇습니다.

Advertisement본문 중간 · 반응형본 도메인에서만 게재

[code lang="text"] POST /orders Idempotency-Key: 8f3a1c2e-4b6d-49a1-9b7d-2e5c1a0f7d33

첫 요청 → 주문 생성, 결과를 키와 함께 저장 같은 키 재요청 → 새 주문을 만들지 않고 "이미 처리됨 + 같은 결과" 반환 [/code]

이렇게 하면 사용자가 버튼을 두 번 누르거나, 통신 오류로 클라이언트가 자동 재시도해도 결과는 항상 한 건입니다. 재시도를 안전하게 만든다는 점에서, 장애 시 대체 경로로 흘리는 폴백(Fallback) 설계와 함께 시스템 신뢰성의 양대 축으로 묶입니다.

기획자·PM이 정해야 하는 것들

멱등성은 개발자가 구현하지만, 무엇을 멱등하게 볼지 정하는 건 기획·운영의 몫인 경우가 많습니다. 다음 네 가지는 스펙 단계에서 정해 두는 게 좋습니다.

결정 항목정해야 할 질문
키 생성 주체클라이언트가 UUID를 만들 것인가, 서버가 발급할 것인가
보관 기간같은 키를 며칠까지 "같은 요청"으로 볼 것인가 (Stripe는 24시간)
중복 시 응답저장된 원래 결과를 그대로 돌려줄 것인가, 에러로 알릴 것인가
적용 범위결제·주문만인가, 포인트 적립·알림 발송까지 확장할 것인가

특히 마지막 항목이 자주 빠집니다. 결제는 멱등하게 막아 놨는데 "적립"과 "푸시 알림"은 두 번 나가는 사고가 흔합니다. 한 번의 사용자 행동이 만드는 부수 효과 전체를 멱등성 범위로 봐야 합니다.

언제 멱등성을 챙겨야 하나

모든 요청에 멱등성 키를 붙일 필요는 없습니다. 다음 조건에 해당하면 반드시 챙깁니다.

  • 한 번 더 실행되면 돈·재고·신뢰가 새는 동작 (결제, 주문, 환불, 송금, 포인트 적립)
  • 클라이언트가 자동 재시도하는 구간 (네트워크 타임아웃 후 재요청)
  • 외부 시스템과 연동돼 중복이 즉시 사고로 이어지는 흐름

반대로 단순 조회(GET)나 멱등하게 설계된 상태 변경(PUT/DELETE)은 이미 안전하므로 추가 장치가 필요 없습니다. 이렇게 "무엇을 보장할지" 기준을 중앙에서 정해 두는 사고방식은 폐루프 운영 구조와도 결이 같습니다.

한 번 더 눌러도 안전한 시스템

멱등성은 거창한 이론이 아니라, "사용자가 버튼을 두 번 눌러도 사고가 안 나게" 만드는 아주 실용적인 안전장치입니다. 핵심은 단순합니다. 같은 요청이 여러 번 와도 결과는 한 번과 같다. 생성·결제·적립처럼 새로 더하는 동작에는 멱등성 키를 붙여 재시도와 중복 클릭을 흡수하면 됩니다.

기획자라면 "이 동작이 두 번 실행되면 무슨 일이 나는가"를 스펙에 적어 두는 습관이, 개발자라면 결제·주문 엔드포인트마다 멱등성 키를 기본값으로 두는 것이 출발점입니다. 두 번 눌러도 안전한 시스템은 결국 사용자가 믿고 결제 버튼을 누를 수 있게 만드는, 신뢰의 토대입니다.

Advertisement글 최하단 · 띠배너본 도메인에서만 게재