백엔드 면접 용어 사전
API·REST멱등성 키

Idempotency-Key

클라이언트가 부여한 요청 고유 키. 서버가 이를 저장해 재시도 시 결제 중복 처리를 막는다.

클라이언트가 요청에 붙이는 고유 식별자로, 서버가 이를 기억해 중복 처리를 막는 장치.

POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{ "orderId": 1, "amount": 10000 }

무엇을 해결하나

POST는 본질적으로 비멱등이다. 그런데 재시도는 피할 수 없다.

  • 결제 요청 → 타임아웃
  • 처리됐는지 알 수 없다
  • 재시도하면 이중 결제, 안 하면 결제 누락

같은 키로 재시도하면 서버가 "이미 처리한 요청"임을 알아본다. Stripe·Toss 등 결제 API가 표준적으로 제공하는 기능이다.

서버 처리 흐름

다이어그램 로딩 중…

구현 — 동시 요청까지 고려해야 한다

CREATE TABLE idempotency (
  key           VARCHAR(64) PRIMARY KEY,
  status        VARCHAR(16),      -- PROCESSING / COMPLETED
  request_hash  VARCHAR(64),
  response_body JSON,
  status_code   INT,
  created_at    TIMESTAMP
);

핵심은 "먼저 선점" 이다.

INSERT INTO idempotency (key, status) VALUES (?, 'PROCESSING');
-- 유니크 제약 위반 → 이미 누가 처리 중이거나 완료함

조회 후 삽입하면 그 사이에 다른 요청이 끼어들 수 있다(경쟁 조건). 삽입을 먼저 시도해 DB의 유니크 제약으로 원자적으로 판정하는 것이 정석이다.

Redis라면 SET key value NX EX 86400 한 줄로 같은 효과를 낸다.

응답까지 저장하는 이유

❌ "이미 처리했습니다" 라고만 응답

  • 클라이언트가 결제 ID를 못 받는다. 원래 응답이 필요하다

✅ 첫 요청의 응답 본문·상태 코드를 저장했다가 그대로 반환

  • 클라이언트 입장에서는 첫 요청이 성공한 것과 완전히 동일

요청 본문 검증 — 놓치기 쉬운 부분

같은 키인데 다른 내용으로 요청이 오면?

Idempotency-Key: abc, amount: 10000   (첫 번째)
Idempotency-Key: abc, amount: 99999   (두 번째)  ← 다른 요청!

요청 본문의 해시를 함께 저장해 다르면 422로 거부한다. 키 재사용 실수나 공격을 막는다.

키는 누가 만드나

클라이언트가 만든다. 서버가 만들면 의미가 없다 — 재시도할 때 같은 키를 보낼 수 있어야 하기 때문이다.

// 화면 진입 시 또는 폼 작성 시작 시 한 번 생성
const key = crypto.randomUUID();
// 재시도 시에도 같은 키를 사용   ← 이것이 핵심

주의 — 재시도할 때마다 새 키를 만들면 아무 의미가 없다. 클라이언트 구현에서 실수하기 쉬운 지점이다.

보관 기간

너무 짧으면 → 늦은 재시도를 못 막는다

  • 너무 길면 — → 저장소가 계속 커진다

일반적으로 24시간 ~ 7일. 결제는 보통 24시간

TTL을 두고 만료시키되, 결제처럼 중요한 것은 별도의 정합성 검증 배치를 함께 둔다.

어디에 적용하나

✅ 결제, 송금, 포인트 적립/차감 ✅ 주문 생성 ✅ 외부 시스템 연동 (메일 발송, SMS) ❌ 단순 조회 (이미 멱등) ❌ 멱등하게 설계할 수 있는 것 (조건부 UPDATE로 충분)

모든 API에 붙일 필요는 없다. 상태 전이로 멱등해지는 것은 그렇게 하고, 그것으로 부족한 "생성" 계열에만 적용한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — API·REST 설계 — 멱등성·상태코드·버저닝·GraphQL