API Idempotency (재시도 안전성, 구현 복잡도, 적용 범위)

멱등성(Idempotency)을 보장하면 시스템이 안정적으로 작동한다는 말, 정말 맞는 걸까요? 실제 주문 생성 API를 운영하면서 멱등성 구조를 도입한 경험이 있습니다. 네트워크 지연으로 동일한 주문 요청이 여러 번 전달되면서 중복 주문이 발생했고, 이를 해결하려고 멱등성 보장 로직을 구현했습니다. 결과적으로 중복 문제는 사라졌지만, 예상보다 훨씬 많은 설계 비용이 들었습니다. 이 경험을 바탕으로 API 멱등성이 어떤 안정성을 제공하는지, 그리고 어느 정도의 복잡성을 감수해야 하는지 구체적으로 정리해보려 합니다.

재시도 안전성 확보

멱등성(Idempotency)이란 동일한 요청을 여러 번 수행하더라도 결과가 변하지 않는 성질을 의미합니다. 쉽게 말해, 같은 요청을 한 번 보내든 열 번 보내든 시스템 상태가 동일하게 유지된다는 뜻입니다. 이 개념은 특히 분산 시스템에서 네트워크 오류가 발생했을 때 빛을 발합니다. 클라이언트가 요청을 보냈는데 응답을 받지 못하면 자동으로 재시도를 하게 되는데, 이때 멱등성이 보장되지 않으면 동일한 작업이 중복으로 처리될 수 있습니다.

REST API 설계 원칙에서는 PUT과 DELETE 메서드가 기본적으로 멱등성을 가지도록 권장합니다. PUT은 특정 리소스를 특정 상태로 변경하기 때문에 여러 번 요청해도 최종 결과가 같고, DELETE는 이미 삭제된 리소스를 다시 삭제해도 "없음" 상태가 유지되기 때문입니다. 반면 POST는 기본적으로 멱등하지 않습니다. 같은 데이터로 POST를 여러 번 보내면 새로운 리소스가 계속 생성되기 때문입니다. 저희 서비스에서도 POST로 주문을 생성할 때 이 문제가 발생했습니다. 고객이 주문 버튼을 한 번만 눌렀는데 네트워크 타임아웃으로 앱이 자동 재시도를 하면서 중복 주문이 들어간 겁니다.

이 문제를 해결하기 위해 Idempotency Key 전략을 도입했습니다. 클라이언트가 요청을 보낼 때마다 고유한 식별자를 헤더에 포함시키고, 서버는 이 키를 기준으로 동일한 요청인지 판단하는 방식입니다. 예를 들어 주문 생성 요청에 'order-12345-abc'라는 키가 포함되어 있으면, 서버는 먼저 이 키로 이미 처리된 요청인지 확인합니다. 이미 처리된 요청이라면 새로 주문을 생성하지 않고 기존 결과를 그대로 반환합니다. 이 방식을 적용한 뒤로 중복 주문 문제는 완전히 사라졌습니다. 국내 금융권과 해외 결제 서비스(Stripe, PayPal 등)에서도 이 방식을 널리 사용하고 있습니다(출처: Stripe API Documentation).

구현 복잡도 증가

멱등성을 보장하는 것은 이론상 간단해 보이지만, 구현 과정에서는 생각보다 많은 고민이 필요합니다. 가장 먼저 부딪히는 문제는 요청의 고유성을 어떻게 식별할 것인가 하는 점입니다. 클라이언트가 보내는 Idempotency Key를 서버가 어디에 저장할지, 얼마나 오래 보관할지, 키가 충돌하면 어떻게 처리할지 등 세부 설계가 필요합니다. Redis를 사용해서 키와 요청 결과를 24시간 동안 캐싱하는 방식으로 구현했습니다. 이렇게 하면 동일한 키로 재요청이 들어왔을 때 빠르게 캐시에서 결과를 반환할 수 있습니다.

하지만 여기서 끝이 아닙니다. 요청 처리 중에 서버가 다운되거나 타임아웃이 발생하면 어떻게 할 것인가도 고려해야 합니다. 예를 들어 주문 생성 요청을 받아서 처리 중인데 데이터베이스 트랜잭션이 중간에 실패하면, 해당 키를 어떤 상태로 저장해야 할까요? 요청 처리 상태를 'pending', 'completed', 'failed' 세 가지로 나누어 관리했습니다. 동일한 키로 재요청이 들어왔을 때 상태가 'completed'면 기존 결과를 반환하고, 'pending'이면 처리 중이라고 응답하며, 'failed'면 다시 시도할 수 있도록 했습니다. 이런 상태 관리 로직만으로도 초기 설계에 2주 이상이 소요됐습니다.

또한 멱등성 보장을 위해서는 데이터베이스 스키마 설계도 신경 써야 합니다. 중복 요청을 빠르게 감지하려면 Idempotency Key에 대한 인덱스가 필요하고, 키와 결과를 함께 저장할 별도 테이블이나 캐시 구조가 필요합니다. 이러한 추가 저장소는 시스템 리소스를 소비하며, 저장 기간이 길어질수록 관리 비용도 증가합니다. 실제 서비스에서는 멱등성 보장을 위한 Redis 캐시만으로도 월 수십만 원의 인프라 비용이 추가로 발생했습니다. 한국정보통신기술협회(TTA)의 API 설계 가이드에서도 멱등성 구현 시 상태 관리와 저장소 설계를 중요하게 다루고 있습니다(출처: 한국정보통신기술협회).

적용 범위 선택

모든 API에 멱등성을 보장하는 것은 현실적으로 불가능하며, 필요하지도 않습니다. 멱등성은 데이터 무결성이 중요한 영역에 선택적으로 적용해야 합니다. 경험상 다음과 같은 기준으로 판단하는 것이 효과적입니다.

  1. 결제, 주문 생성처럼 중복 시 금전적 손실이 발생하는 경우 - 반드시 멱등성 보장
  2. 회원가입, 비밀번호 변경처럼 중복 시 데이터 불일치가 발생하는 경우 - 멱등성 보장 권장
  3. 단순 조회나 통계 집계처럼 중복 요청이 시스템에 영향을 주지 않는 경우 - 멱등성 보장 불필요

저희 서비스에서는 주문 생성과 결제 API에만 멱등성을 적용했습니다. 반면 상품 목록 조회나 장바구니 조회 같은 읽기 작업에는 적용하지 않았습니다. 이런 작업은 여러 번 요청해도 데이터가 변하지 않기 때문에 자연스럽게 멱등하며, 굳이 추가 로직을 구현할 필요가 없습니다. 또한 로그 수집이나 이벤트 발행처럼 매번 새로운 데이터를 생성해야 하는 경우에도 멱등성을 강제로 적용하면 오히려 기능이 제대로 작동하지 않습니다.

멱등성 적용 범위를 결정할 때는 비즈니스 영향도를 먼저 따져봐야 합니다. 중복 요청이 발생했을 때 고객에게 어떤 피해가 가는지, 데이터 정합성 문제가 얼마나 심각한지를 기준으로 판단하는 게 좋습니다. 솔직히 처음에는 모든 POST 요청에 멱등성을 적용해야 한다고 생각했는데, 실제로 적용해보니 필요 없는 곳에 쓸데없이 복잡도만 올라가더군요. 결국 핵심 API에만 집중하는 게 훨씬 효율적이었습니다.

API 멱등성은 시스템 안정성을 확보하는 데 분명히 중요한 역할을 합니다. 특히 분산 환경에서 네트워크 오류가 빈번하게 발생하는 상황이라면 멱등성 보장은 필수적입니다. 하지만 이를 위해서는 상태 관리, 저장소 설계, 예외 처리 등 상당한 설계 복잡성을 감수해야 합니다. 멱등성은 모든 API에 일괄적으로 적용하기보다는 데이터 무결성이 중요한 핵심 영역에 선택적으로 적용하는 것이 현실적입니다. 설계 초기 단계에서 어떤 API가 멱등성을 필요로 하는지 명확히 파악하고, Idempotency Key 전략과 상태 관리 방식을 구체적으로 설계한다면 안정성과 복잡성 사이의 균형을 찾을 수 있을 것입니다.

댓글

이 블로그의 인기 게시물

HTTP 메서드의 필요성 (GET과 POST, PUT과 DELETE, API 보안)

API 없는 세상의 불편함 (로그인 연동, 서비스 구조, 디지털 인프라)

API 이해하기 (서비스 연결, 시스템 협력, 디지털 구조)