2 min read

Idempotency Key API 설계

중복 요청을 안전하게 처리하기 위한 키 스코프, TTL, 응답 재사용 정책 정리

Idempotency Key API 설계 thumbnail

도입

결제, 주문, 포인트 적립처럼 중복 실행이 치명적인 기능에서는 네트워크 재시도를 허용하는 순간 데이터 정합성 문제가 시작된다. 클라이언트 재시도, 게이트웨이 타임아웃, 메시지 브로커 재전송이 겹치면 같은 요청이 여러 번 처리될 수 있다. 이 글은 Idempotency-Key를 API 계약으로 정의하고 저장소/TTL/응답 캐시를 설계하는 실전 가이드를 정리한다.

Idempotency Key API 설계 커버
Wikimedia Commons 기반 무료 이미지

문제 정의

중복 처리 이슈는 보통 대형 장애 후에 발견되지만 원인은 사전에 예측 가능하다. 다음 징후가 보이면 즉시 설계를 보완해야 한다.

  • 동일 요청을 구분할 키 생성 규칙이 없어 재시도와 신규 요청을 식별할 수 없다.
  • 성공 응답을 저장하지 않아 재시도 시 결과가 달라진다.
  • 키 보관 TTL이 비즈니스 보장 시간보다 짧아 늦은 재시도에서 중복 반영이 발생한다.

Idempotency는 단순 키 체크가 아니라 상태 머신이다. IN_PROGRESS, SUCCEEDED, FAILED_RETRIABLE를 분리하면 장애 대응이 쉬워진다.

핵심 개념

관점설계 기준검증 포인트
키 생성클라이언트가 단위 작업별 UUID 생성중복률과 충돌률
상태 저장요청 본문 해시 + 응답 본문 저장재시도 시 응답 일관성
TTL 정책비즈니스 보장 시간 이상 유지늦은 재시도 차단 여부
관측성키 단위 로그/메트릭 수집중복 요청 탐지 시간

핵심은 API 레벨에서 계약을 명시하는 것이다. 서버만 구현하고 클라이언트 가이드를 누락하면 중복 문제는 재발한다.

코드 예시 1: Idempotency 저장소 인터페이스

export type IdempotencyRecord = {
  key: string;
  requestHash: string;
  status: "IN_PROGRESS" | "SUCCEEDED" | "FAILED_RETRIABLE";
  responseCode?: number;
  responseBody?: string;
  expiresAt: Date;
};

export interface IdempotencyStore {
  begin(record: Omit<IdempotencyRecord, "status">): Promise<"CREATED" | "EXISTS">;
  complete(key: string, responseCode: number, responseBody: string): Promise<void>;
  find(key: string): Promise<IdempotencyRecord | null>;
}

코드 예시 2: API 핸들러 처리 루틴

export async function createOrderHandler(req: Request) {
  const key = req.headers.get("Idempotency-Key");
  if (!key) return new Response("Missing Idempotency-Key", { status: 400 });

  const body = await req.json();
  const requestHash = await sha256(JSON.stringify(body));
  const started = await idempotencyStore.begin({ key, requestHash, expiresAt: plusHours(24) });

  if (started === "EXISTS") {
    const record = await idempotencyStore.find(key);
    if (!record) return new Response("Conflict", { status: 409 });
    return new Response(record.responseBody, { status: record.responseCode ?? 202 });
  }

  const result = await orderUseCase.execute(body);
  await idempotencyStore.complete(key, 201, JSON.stringify(result));
  return new Response(JSON.stringify(result), { status: 201 });
}

아키텍처 흐름

Mermaid diagram rendering...

트레이드오프

  • 응답 재사용을 위해 저장 공간이 늘어나지만 결제/주문 정합성 비용보다 저렴하다.
  • 키 TTL을 길게 두면 안전하지만 저장소 비용과 GDPR 삭제 정책을 함께 고려해야 한다.
  • 요청 해시 검증을 넣으면 보안이 강화되지만 CPU 비용이 증가한다.

정리

Idempotency-Key는 재시도 허용 시스템의 안전장치다. 키 생성 규칙, 저장 상태, 응답 재사용 정책을 함께 설계하면 장애 상황에서도 비즈니스 정합성을 유지할 수 있다.

이미지 출처

  • Cover: source link
  • License: CC BY-SA 4.0 / Author: Papapep
  • Note: Wikimedia Commons 무료 라이선스 이미지를 다운로드 후 1600px 기준 JPG로 최적화했습니다.

댓글