멱등성 키를 UUID로 만드는 이유와, 클라이언트에서 UUID로 멱등성 키를 처리하는 방법을 정리했습니다

개요

클라이언트는 서버에 같은 요청을 여러 번 보낼 수 있습니다. 중복된 요청이 서버에 전달되어 같은 작업이 반복 수행되는 것을 막으려면 멱등성(idempotency)을 보장하는 방법이 필요합니다

멱등성을 깊이 고민하게 된 건 한 프로젝트에서 콘텐츠 관리 기능을 구현할 때였습니다. 클라이언트와 서버, 그리고 외부 결제 연동 사이의 통신을 일관되게 유지하는 방법을 찾아야 했습니다

이 글에서는 멱등성 키를 만들 때 UUID(Universally Unique Identifier)를 쓰는 이유를 정리하고, 클라이언트에서 UUID로 멱등성 키를 처리하는 방법을 예시로 남깁니다

멱등성이란 무엇인가

멱등성은 시스템이나 서비스에서 같은 작업을 여러 번 수행해도 결과(response)가 변하지 않는 성질입니다

HTTP 메서드 중 GET, PUT, DELETE는 멱등성을 자연스럽게 갖추고 있고, POSTPATCH는 보장되지 않는 경우가 많습니다. 특히 POST 요청은 리소스를 생성하거나 상태를 변경하기 때문에 중복 요청 시 불필요한 작업이 일어날 수 있어, 필요한 경우 멱등성 키를 씁니다

멱등성 키는 왜 필요한가

네트워크 장애나 클라이언트의 실수로 같은 요청이 여러 번 서버에 도달할 때, 이를 처리하려고 멱등성 키를 사용합니다. 서버는 같은 작업이 중복 처리되는 것을 막고, 클라이언트는 요청의 결과를 예상할 수 있습니다

예를 들면 이렇습니다

  1. 클라이언트가 주문을 생성하는 POST 요청을 서버에 보냅니다
  2. 요청 중 네트워크 장애로 클라이언트가 요청을 다시 보내지만, 서버는 이미 주문이 생성된 상태로 인식하고 중복된 주문을 처리하지 않습니다

이때 서버가 중복 요청을 처리하지 않도록 보장하는 것이 멱등성 키입니다. 이 키는 요청이 중복됐는지 판단하는 기준이 되어, 같은 요청에 대해 서버는 같은 결과만 반환합니다

클라이언트에서 멱등성 키를 왜 UUID로 만드나

멱등성 키를 꼭 UUID로 만들어야 하는 건 아닙니다. 멱등성 키의 예시가 거의 UUID로만 되어 있어서 UUID만 가능하다고 착각하기 쉽지만, 앞서 말한 대로 멱등성 키의 역할은 같은 요청에 대해 같은 결과를 얻는 것입니다. 그래서 클라이언트에서 요청 event를 연속으로 발생시켰을 때 똑같은 멱등성 키를 header에 넣어야, 클라이언트는 한 번만 클릭한 케이스에서 원하는 예상 값을 받을 수 있습니다

필수가 아니지만 UUID를 쓰는 이유

  1. 고유성 보장: v4 UUID는 122비트의 랜덤 값이라, 수조 개를 생성하더라도 중복이 발생할 확률이 사실상 무시할 만큼 낮습니다
  2. 예측 불가능성: v4 UUID는 랜덤한 값으로 생성되어 다음 값을 예측하기 어렵습니다 (반면 v1 UUID는 시간·MAC 주소 기반이라 예측이 가능합니다)
  3. 표준화·범용성: UUID는 128비트(문자열로는 36자)의 표준 포맷이라 언어나 플랫폼에 상관없이 다루기 쉽습니다. 다만 정수 키보다 크기가 커서, 데이터베이스 인덱스에서는 크기·성능 면에서 트레이드오프가 있습니다
  4. 간편성: UUID는 중앙 시스템에 등록하고 발급하는 과정이 없어서 더 빠르고 간단하게 만들 수 있습니다

연속적인 HTTP 요청에서 UUID로 멱등성 키 처리하기

클라이언트에서 멱등성 키를 UUID로 생성해 연속적인 POST 또는 PATCH 요청을 처리하는 방식은 다음과 같습니다

1. 요청 전 UUID 생성

클라이언트는 요청을 보내기 전에 UUID를 생성합니다. 이 UUID는 요청이 중복됐는지 판단하는 기준이 됩니다

// UUID 생성 (예시 - JavaScript에서 uuid 패키지를 사용)
const requestId = uuidv4();

2. UUID를 요청에 포함

생성된 UUID는 요청의 헤더나 바디에 포함되어 서버로 전송됩니다. 예를 들어 HTTP 헤더에 Idempotency-Key라는 이름으로 UUID를 넣을 수 있습니다

// POST 요청 시 멱등성 키 포함
fetch('https://example.com/orders', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Idempotency-Key': requestId,
  },
  body: JSON.stringify({
    itemId: 123,
    quantity: 2,
  }),
});

맺음말

이 글을 쓰게 된 결정적인 이유가 있습니다. 구글링을 하다 보면 클라이언트에서 멱등성 키를 생성하는 방식이 "클라이언트가 POST 또는 PATCH 요청을 보낼 때마다 UUID를 생성하고 이를 요청에 포함시키는 방식"이라고 설명되어 있습니다

그래서 저는 매번 요청을 보낼 때마다 새로운 UUID를 보내고, 서버에서 DB나 캐시 저장소에 저장하고 비교해서 처리해준다고 생각했습니다. 예시가 UUID밖에 없는 이유도 예측 불가능한 고유 식별자가 필요해서라고 한쪽으로 치우쳐 판단했습니다. 멱등성의 뜻과 다르게 생각하고 있었던 것입니다

멱등성의 본래 의미에 맞게 보면, 클라이언트에서 멱등성 키를 UUID로 생성하는 이유는 여러 번의 요청이 같은 결과를 내도록 보장하기 위해서입니다. 하나의 논리적 요청 단위마다 UUID를 생성하고, 재시도할 때는 같은 키를 유지해서 요청에 포함시키는 방식이 서버와 클라이언트 간의 일관된 상태 유지를 돕습니다. 이렇게 하면 중복 요청이 들어왔을 때 서버가 이를 정확히 인식하고 같은 결과를 반환할 수 있습니다. 분산 시스템에서 발생할 수 있는 요청 중복도 효과적으로 처리할 수 있습니다

클라이언트가 UUID를 생성해 요청에 포함시키는 것은 단순히 고유한 식별자를 쓰는 것을 넘어, 멱등성을 보장하는 핵심 방식이자 시스템의 일관성과 안정성을 높이는 역할을 합니다