멱등성(Idempotency)개념과 멱등성 키(Idempotency Key)
들어가며
백엔드 개발을 하다 보면 한 번쯤은 이런 고민을 하게된다.
"이 API가 두 번 호출되면 어떻게 되지?
특히 결제, 재고 차감, 포인트 지급과 같은 기능에서는 이 질문이 굉장히 중요해진다. 단순히 한 번 실행되어야 하는 로직이 두 번 실행된다면, 서비스에 직접적인 금전적 손실이 발생할 수도 있기 때문이다. 이러한 문제의 근본적인 원인은 대부분 중복 요청에서 발생하며, 이를 안전하게 처리하기 위한 개념이 바로 멱등성(Idempotency) 이다. 이번 글에서는 중복 요청이 발생하는 상황부터 시작해서, 이를 해결하기 위한 멱등성 개념과 멱등성 키(Idempotency Key)에 대해 정리해보겠다.
1. 개요
1.1. 중복 요청은 왜 발생할까?
개발을 처음 할 때는 '같은 요청이 두 번 들어올 일은 없겠지'라고 생각하기 쉽다. 하지만 실제 서비스 환경에서는 중복 요청이 생각보다 자주 발생한다. 가장 흔한 경우는 사용자의 행동에서 비롯된다. 사용자가 버튼을 두 번 클릭하거나, 네트워크가 느려서 응답이 늦어질 때 다시 요청을 보내는 경우가 대표적이다. 여기에 더해 모바일 환경에서는 네트워크 상태에 따라 요청이 자동으로 재전송되기도 한다. 또한 의도적인 경우도 존재한다. 특정 API를 반복 호출해서 시스템의 허점을 노리는 경우나, 단순히 여러 번 클릭하는 상황도 충분히 발생할 수 있다. 이처럼 중복 요청은 개발자가 의도하지 않았더라도 언제든 발생할 수 있으며, 특히 결제나 재고와 같은 중요한 도메인에서는 반드시 고려해야 할 문제다.
1.2. 레이어별 방어 전략
이러한 중복 요청을 막기 위해 일반적으로는 여러 레이어에서 방어 전략을 세운다. 먼저 프론트엔드에서는 버튼을 클릭하면 비활성화하거나, 로딩 상태를 표시하여 추가 클릭을 막는 방식이 많이 사용된다. 또한 요청이 진행 중인지 여부를 상태값으로 관리하여 중복 요청을 제한하기도 한다. 하지만 프론트 레벨에서의 방어는 완전하지 않다. 페이지를 새로고침하면 상태가 초기화되기 때문에 다시 요청이 가능해지고, 네트워크 오류로 인한 재요청은 제어할 수 없다. 데이터베이스 레벨에서는 유니크 제약 조건을 통해 중복 데이터를 막을 수 있다. 예를 들어 동일한 주문 ID가 두 번 저장되지 않도록 제한할 수 있다. 그러나 이 방식 역시 한계가 있다. 데이터베이스는 내부 상태만 보호할 수 있을 뿐, 외부 API 호출까지 제어하지는 못한다. 만약 결제 API를 두 번 호출한 뒤 DB에서 한 번만 저장된다면, 실제로는 결제가 두 번 이루어지는 문제가 발생할 수 있다. 결국 중복 요청을 완전히 안전하게 처리하기 위해서는 애플리케이션 레벨에서의 제어가 필요하다.
1.3. 멱등성이란?
이러한 문제를 해결하기 위해 등장한 개념이 바로 멱등성(Idempotency)이다. 멱등성이란, 같은 연산을 여러 번 수행하더라도 결과가 동일하게 유지되는 성질을 의미한다. 예를 들어, 어떤 API가 멱등성을 가진다면 동일한 요청을 한 번 보내든, 두 번 보내든, 열 번 보내든 결과는 항상 동일해야 한다. 이 개념은 특히 네트워크 환경에서 매우 중요하다. 요청이 중복으로 전달될 수 있다는 전제를 두고, 시스템이 그 상황에서도 안전하게 동작하도록 설계하는 것이기 때문이다.
2. 멱등성 키 (Idempotency Key)
2.1. 기본 개념
멱등성을 구현하는 방법은 여러 가지가 있지만, 가장 대표적인 방식이 바로 멱등성 키를 사용하는 것이다. 멱등성 키는 '이 요청이 동일한 요청인지 식별하기 위한 고유 값'이다. 클라이언트는 요청을 보낼 때 이 키를 함께 전달하고, 서버는 해당 키를 기준으로 이미 처리된 요청인지 여부를 판단한다.
2.2. 동작 방식
멱등성 키 기반의 처리는 다음과 같은 흐름으로 동작한다. 클라이언트는 요청을 생성할 때, 해당 요청을 대표하는 고유한 키를 함께 생성한다. 이 키는 일반적으로 UUID 형태로 생성되며, HTTP Header에 포함되어 서버로 전달된다. 서버는 요청을 받으면 가장 먼저 이 멱등성 키가 이미 존재하는지 확인한다. 이때 Redis와 같은 빠른 저장소를 사용하는 경우가 많다.
만약 해당 키가 존재하지 않는다면, 서버는 이 요청을 새로운 요청으로 간주하고 실제 비즈니스 로직을 수행한다. 예를 들어 결제 요청이라면 실제 결제 API를 호출하고, 그 결과를 저장한다. 이후 멱등성 키와 함께 결과를 저장소에 기록한다. 반대로 동일한 키가 이미 존재한다면, 서버는 비즈니스 로직을 다시 실행하지 않는다. 대신 이전에 저장해둔 결과를 그대로 반환한다.
이 방식의 핵심은 '요청을 한 번만 실행하고, 이후에는 결과만 재사용한다'는 점이다. 따라서 동일한 요청이 여러 번 들어오더라도 내부적으로는 한 번만 처리되며, 외부에서는 정상적인 응답을 계속 받을 수 있다.
2.3. 키 관리 시 주의할 점
멱등성 키를 사용할 때는 몇 가지 중요한 고려사항이 있다. 먼저, 키는 요청 단위가 아니라 사용자의 의도 단위로 생성되어야 한다. 예를 들어 동일한 결제를 재시도하는 경우에는 같은 키를 사용해야 하지만, 완전히 새로운 주문이라면 새로운 키를 생성해야 한다. 또한 멱등성 키는 일정 시간이 지나면 삭제되어야 한다. 키를 영구적으로 저장하면 저장소에 불필요한 데이터가 계속 쌓이게 되기 때문이다. 일반적으로는 몇 분에서 수십 시간 정도의 TTL을 설정하여 관리한다. 마지막으로, 멱등성 키를 저장하는 위치와 방식도 중요하다. 메모리, Redis, DB 등 다양한 선택지가 있지만, 성능과 일관성을 고려하여 적절한 저장소를 선택해야 한다.
2.4. 언제 사용하는가?
멱등성 키는 특히 다음과 같은 상황에서 유용하게 사용된다.
- 결제 처리와 같이 중복 실행이 치명적인 경우
- 재고 감소처럼 정확성이 중요한 경우
- 외부 API 호출이 포함된 로직
- 동일 요청이 재시도될 가능성이 높은 환경
다만 모든 API에 멱등성을 적용할 필요는 없다. 단순 조회 API나, 여러 번 실행되어도 문제가 없는 로직에서는 오히려 불필요한 복잡도를 증가시킬 수 있다. 따라서 멱등성 키는 중복 실행이 문제를 일으킬 수 있는 지점에 선택적으로 적용하는 것이 바람직하다.
3. 개인적 견해
멱등성 키를 처음 접했을 때는 중복 요청을 막는 기술이라고 생각하기 쉽다. 하지만 실제로는 조금 다른 관점이 필요하다. 중복 요청은 완전히 막기 어렵다. 사용자 행동, 네트워크 환경, 시스템 구조 등 다양한 요소 때문에 언제든 발생할 수 있다. 따라서 중요한 것은 중복 요청을 차단하는 것이 아니라, 중복 요청이 발생하더라도 시스템이 안전하게 동작하도록 만드는 것이다. 이런 관점에서 보면 멱등성 키는 단순한 구현 기법이 아니라, 안정적인 API 설계를 위한 하나의 패턴이라고 볼 수 있다.
마치며
멱등성은 단순한 개념이지만, 실제 서비스에서는 매우 중요한 역할을 한다. 특히 결제, 주문, 재고와 같은 핵심 기능을 다루는 경우라면 '이 요청이 여러 번 들어오면 어떻게 될까?'라는 질문을 반드시 던져봐야 한다. 그리고 그 답이 명확하지 않다면, 멱등성 키를 도입할 시점일 가능성이 높다. 결국 좋은 시스템은 오류가 발생하지 않는 시스템이 아니라, 오류가 발생하더라도 안전하게 동작하는 시스템이라고 생각한다.