요즘IT
위시켓
최근 검색어
전체 삭제
최근 검색어가 없습니다.

국내 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하고 있을까요?

회원가입을 하면 원하는 문장을
저장할 수 있어요!

다음

회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!

확인

개발

프로그래밍 용어 ‘멱등성(Idempotent)’ 알아보기

년차,
어떤 스킬
,
어떤 직무
독자들이 봤을까요?
어떤 독자들이 봤는지 궁금하다면?
로그인

국내 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하고 있을까요?

 

이번 글은 토스의 페이테크 계열사 '토스페이먼츠'의 기술 블로그가 멱등성(Idempotent)이라는 컴퓨터 과학 용어를 쉽게 설명한 글을 소개합니다.

 

멱등(Idempotent)하다는 것

생소한 표현이지만 알고 보면 쉽습니다. 컴퓨터 과학에서 멱등하다는 것은 첫 번째 수행을 한 뒤 여러 차례 적용해도 결과를 변경시키지 않는 작업 또는 기능의 속성을 뜻하는데요. 즉, 멱등한 작업의 결과는 한 번 수행하든 여러 번 수행하든 같습니다.

 

예를 들어, 어떤 숫자에 1을 곱하는 연산은 여러 번 수행해도 처음 1을 곱한 것과 같은 숫자가 되기 때문에 멱등합니다. 마찬가지로 숫자의 절대값을 계산하는 절대값 함수는 같은 값에 대해 여러 번 수행해도 처음과 항상 같은 숫자가 돌아오기 때문에 멱등 함수라고 부릅니다.

 

 

HTTP 메서드의 멱등성

HTTP 메서드에도 멱등성이 있습니다. 예를 들어 GET은 여러 번 호출해도 같은 결과가 돌아오고, 리소스에 변화를 일으키지 않기 때문에 멱등성이 보장된 메서드입니다.

 

GET, PUT처럼 리소스를 조회하거나 대체하는 메서드는 멱등한데요. PUT은 여러 번 호출해도 매번 같은 리소스로 업데이트되기 때문에 결과가 달라지지 않죠. DELETE 역시 여러 번 호출해도 삭제된 리소스에 대한 결과는 달라지지 않습니다. 반면 서버 데이터를 변경하는 POST, PATCH는 호출할 때마다 응답이 달라지기 때문에 멱등한 메서드가 아닙니다. 이렇게 멱등하지 않은 메서드에 멱등성을 제공하려면 서버에서 멱등성을 구현해야 합니다.

 

HTTP 메서드의 안전성과 멱등성은 어떻게 다를까요?

HTTP 메서드의 주요 속성에는 멱등성 외에도 안전성이 있는데요. 안전성이 보장된 메서드는 리소스를 변경하지 않습니다. GET, HEAD, OPTIONS는 안전한 메서드죠. 안전성이 보장된 메서드는 멱등성도 보장하지만, 멱등성을 지닌 메서드가 항상 안전성을 보장하지는 않습니다. 예를 들어 PUT과 DELETE는 멱등한 메서드지만, 리소스에 변화를 일으키기 때문에 안전한 메서드는 아닙니다.

(참고: RFC 7231)

 

 

API 관점에서 바라보기

멱등성을 API 관점에서 살펴볼까요? 멱등한 API라면 두 번 이상 요청해도 결과는 처음 요청과 똑같이 돌아옵니다. 단순히 돌아온 값이 같을 뿐 아니라 서버 상태(DB)에도 영향을 미치지 않죠. 이렇게 시스템에 의도하지 않은 문제를 일으키지 않고 요청을 재시도할 수 있기 때문에, 멱등성은 결함 없고 안전한 API를 만드는데 중요합니다.

 

사용자가 결제하는 시점에 네트워크 오류나 타임아웃으로 인해 결과를 받지 못하는 시나리오를 한 번 생각해 볼까요? 멱등성이 보장되지 않은 결제 API라면 실제로 결제가 성공했는지 수동으로 확인해야 하고, 확인해 보니 실제로 결제가 되지 않았다면 고객이 같은 결제를 다시 시도해야 합니다. 반면 결제 API가 멱등하다면 다시 같은 요청을 보내지 않고 전에 받지 못한 결과만 다시 받을 수 있을 때 편리할 겁니다. 또 실수로 중복 요청이 되더라도(일명 ‘따닥’) 실제로는 결제가 되지 않아서 안심하고 여러 번 요청할 수 있습니다.

 

 

멱등한 요청인지 알 수 있는 방법

멱등성을 보장하려면 멱등키를 API 요청에 포함하면 돼는데요. 이전 요청과 동일한 멱등키를 가진 요청을 받으면 서버에서 이 요청을 중복으로 판단한 뒤 실제로 처리하지 않고 첫 요청과 같은 응답을 반환하는 방식이죠. 요청 본문, URL 쿼리 매개변수, 헤더 중 하나에 멱등키를 포함해서 보내면 됩니다. IETF에서는 요청 헤더에 포함하는 방법을 표준으로 제안하고 있어요.

토스페이먼츠도 아래처럼 헤더에 포함하는 방식으로 멱등키를 지원합니다.

 

Idempotency-Key: {IDEMPOTENCY_KEY}

 

curl --request POST \\
  --url <https://api.tosspayments.com/v1/payments/5zJ4xY7m0kODnyRp/cancel> \\
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \\
  --header 'Content-Type: application/json' \\
  --header 'Idempotency-Key: SAAABPQbcqjEXiDL' \\
  --data '{"cancelReason":"고객 변심"}'

 

그런데 헤더에 키를 추가하는 것 만으로 같은 요청이 반복된 건지 어떻게 식별해서 처리할 수 있을까요? 예시로 결제 취소 플로우를 살펴보겠습니다.

 

  • Step 1. API 서버는 취소 요청마다 헤더에 멱등키가 있는지 확인합니다.
  • Step 2. 또 멱등키를 저장하기 위해 DB를 만들어두고, 멱등키가 포함된 취소 요청이 들어왔을 때 이 DB를 쿼리 해서 요청이 들어온 멱등키와 매칭되는 요청 기록이 있는지 확인합니다.
    • 멱등한 요청 기록을 DB에 저장하는 기간을 정해둘 수 있습니다. 그 기간이 지나면 DB에 저장된 멱등키와 기록이 없기 때문에 같은 멱등키를 사용해서 새로운 요청을 보낼 수 있는데, 사용하는 입장에서는 멱등키의 유효 기간이라고 할 수 있죠.
  • Step 3-1. 만약 이전에 같은 멱등키로 들어온 요청이 있었다면, 서버에서 실제 요청을 실행하지 않고 저장되어 있던 응답 데이터를 돌려줍니다.

 

 

  • Step 3-2. 만약 멱등키와 매칭되는 이전 기록이 없다면, 새로 생성된 응답을 저장하는 새로운 기록을 만들고 응답을 클라이언트에 돌려줍니다.

 

도메인 서버 로직의 복잡도가 높다면 멱등성 로직을 추가했을 때 API 성능 개선에 도움이 되기도 하는데요. 멱등키를 가진 요청은 도메인 서버로 바로 처리되지 않기 때문입니다.

 

토스페이먼츠 서버에서는 멱등한 요청인지 식별하기 위해 API 요청 헤더로 보낸 멱등키와 API 키, API 주소, HTTP 메서드 조합을 확인합니다. 따라서 API 키, API 주소, HTTP 메서드가 다르다면 같은 멱등키를 사용해도 새로운 요청으로 받아들입니다. 자세한 내용은 토스페이먼츠 멱등키 문서를 살펴보세요.

 

 

예제로 이해하기

멱등성이 보장된 결제 취소 API의 처리 프로세스를 아래 의사 코드로도 살펴보겠습니다.

 

클라이언트

  • 헤더에 멱등키를 추가해서 요청해요. 멱등키는 UUID v4와 같이 충분히 무작위적인 고유 값이어야 합니다.
  • 최초 요청 이후에는 다시 요청해도 HTTP 코드 200과 함께 매번 같은 결과가 돌아옵니다.

 

let idempotentKey = generateUUIDv4()

function async cancelPayment(idempotencyKey: string) {
  try {
    return await axios.post("<https://myshop/cancel-payment>",
      {
        orderId: UINQUE_ORDER_ID
        amount: 100,
      },
      {
        headers: {
          "Idempotency-Key": idempotentKey // 헤더에 멱등키를 추가합니다.
        }
      }
    )
  } catch(e) {
    if (e.name === "TIMEOUT") { // 타임아웃이 일어났을 때 같은 요청을 보낼 수 있습니다.
        return await cancelPayment(idempotencyKey)
    }
    console.error("ERROR")
  }
}

const response = await cancelPayment(idempotentKey);

 

서버

  • 멱등성을 지원하는 서버에서는 이렇게 구현합니다. 멱등키 DB에 멱등키와 매칭되는 요청 기록을 추가하고, 취소 처리에 성공했다면 성공 응답을 보내줍니다.
  • 같은 취소 요청이 반복되면 요청에 멱등키가 포함되어 있는지, 이미 저장된 멱등키가 있는지 확인합니다.

 

const idempotencyResponses = new Map();

let cancelReq = {
  orderId: req.body.orderId
  amount: req.body.amount,
};

let idempotencyKey = req.headers.idempotencyKey || null // 요청 헤더에서 멱등키를 가져옵니다.

// 멱등키가 있고 멱등 응답도 저장되어 있다면 실제 처리하지 않고 저장된 응답을 내보냅니다.
if (idempotencyKey != null && idempotencyResponses.has(idempotencyKey)) {
  const response = idempotencyResponses.get(idempotencyKey);
  return res.status(response.status).json(response);
};

const result = cancelProcessor.cancel(cancelReq); // 실제로 취소를 처리합니다.

// 멱등키가 있으면 멱등응답을 저장합니다.
if (idempotencyKey != null) {
  idempotencyResponses.set(idempotencyKey, result);
}

const responseBody = {
  message: `결제 취소 성공`,
};

return res.status(200).json(responseBody);

 

하나의 API가 아니라 여러 API에서 모두 멱등성을 보장하려면 어떻게 해야 할까요? API 마다 멱등성을 구현하는 대신 멱등성 컴포넌트를 만들어서 재사용해보세요. 위 예제 코드에서 본 내용 중 실제 취소 처리를 위한 로직을 제외하고 멱등키 처리만을 위한 로직을 가지고 멱등성 컴포넌트를 만들면 됩니다.

 

 

에러 시나리오 알아보기

멱등키를 구현할 때 처리해야 하는 에러 시나리오를 알아보겠습니다. IETF 명세를 살펴보면 아래 세 가지 시나리오에 대응하는 방법을 제안하고 있습니다.

 

서로 다른 요청인데 같은 멱등키를 사용했을 때 처리해야 하는 케이스 두 가지가 각각 409 Conflict, 422 Unprocessable Entity로 처리되어야 합니다.

 

재시도 된 요청 본문(payload)이 처음 요청과 다른데 같은 멱등키를 또 사용했다면 422 Unprocessable Entity 에러를 보내줘야 하는데요. 요청 형식에 문제가 없고, 서버에서 받아들일 수 있는 요청이지만 멱등한 요청이 아니기 때문입니다. 또, 이전 요청 처리가 아직 진행 중일 때 같은 멱등키로 새로운 요청이 온다면 409 Conflict 에러를 보내서 기다렸다가 다시 요청해달라고 안내해주세요.

 

한주연
그래픽 이은호, 이나눔


<참고하면 좋을 자료>

 

<원문>

멱등성이 뭔가요?

 

요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.

좋아요

댓글

공유

공유

토스페이먼츠 기술 블로그
78
명 알림 받는 중

작가 홈

토스페이먼츠 기술 블로그
78
명 알림 받는 중
토스페이먼츠 기술 블로그입니다. 개발자들이 만든, 개발자들을 위한 PG사 토스페이먼츠는 PG를 연동하는 개발자 경험을 수호하기 위해 직관적인 API, 편리한 개발자센터, 읽기 쉬운 문서를 만들고 있습니다.

좋아요

댓글

스크랩

공유

공유

지금 회원가입하고,
요즘IT가 PICK한 뉴스레터를 받아보세요!

회원가입하기
요즘IT의 멤버가 되어주세요! 요즘IT의 멤버가 되어주세요!
요즘IT의 멤버가 되어주세요!
모든 콘텐츠를 편하게 보고 스크랩해요.
모든 콘텐츠를 편하게 보고 스크랩 하기
매주 PICK한 콘텐츠를 뉴스레터로 받아요.
매주 PICK한 콘텐츠를 뉴스레터로 받기
로그인하고 무료로 사용하기