API Idempotency 키 전략 (중복 방지, 구현 복잡성, 데이터 무결성)
저는 처음 결제 API를 구현할 때 Idempotency라는 개념 자체를 몰랐습니다. 네트워크가 불안정한 환경에서 동일한 결제 요청이 두 번 들어와도 "뭐 그럴 수 있지"라고 생각했죠. 하지만 실제로 중복 결제가 발생하고 고객 컴플레인이 들어왔을 때, 제가 얼마나 안일했는지 깨달았습니다. 분산 시스템에서는 네트워크 지연이나 타임아웃으로 인해 동일한 요청이 여러 번 전달되는 상황이 자연스럽게 발생합니다. 특히 결제나 주문 생성처럼 데이터 무결성이 중요한 API에서는 이 문제가 치명적입니다.
중복 방지 메커니즘의 실제 작동 원리
Idempotency 키 전략의 핵심은 각 요청에 고유한 식별자를 부여하는 것입니다. 클라이언트가 UUID나 타임스탬프 기반의 고유 키를 생성해서 요청 헤더에 포함하면, 서버는 이 키를 기준으로 "이 요청을 이미 처리했는가?"를 판단합니다. 제가 직접 구현했을 때는 Redis를 사용해서 키를 저장했는데, 요청이 들어오면 먼저 해당 키가 캐시에 있는지 확인하고 없으면 처리 후 결과를 저장하는 방식이었습니다.
여기서 중요한 건 처리 상태까지 함께 저장해야 한다는 점입니다. 단순히 "처리했다/안 했다"만 기록하면, 첫 요청이 처리 중일 때 두 번째 요청이 들어오면 둘 다 처리될 수 있습니다. 그래서 세 가지 상태를 구분했습니다.
- PROCESSING: 현재 처리 중인 상태로, 같은 키로 들어온 요청은 대기하거나 409 Conflict를 반환
- COMPLETED: 처리 완료 상태로, 저장된 응답 결과를 그대로 반환
- FAILED: 처리 실패 상태로, 재시도를 허용하거나 에러 응답 반환
이런 상태 관리 덕분에 네트워크가 불안정해도 동일한 결제 요청이 두 번 처리되는 일은 없었습니다. 실제로 타임아웃이 발생해서 클라이언트가 재요청을 보냈을 때도, 서버는 첫 요청의 처리 결과를 그대로 돌려줬습니다. 이 방식이 가장 안정적이었습니다.
구현 복잡성과 운영 부담
하지만 솔직히 이 전략을 도입하면서 초기 구현 난이도가 상당히 높아졌습니다. 우선 키를 저장할 저장소를 선택해야 했는데, 데이터베이스는 속도가 느리고 Redis 같은 인메모리 캐시는 휘발성이 문제였습니다. 결국 Redis를 메인으로 쓰고 백업용으로 데이터베이스에도 기록하는 하이브리드 방식을 선택했습니다.
키 관리 정책도 고민이 많았습니다. 키를 무한정 저장할 수는 없으니까 TTL(Time To Live)을 설정해야 하는데, 너무 짧으면 유효한 재요청도 막히고 너무 길면 저장소가 비대해집니다. 결제 API 특성상 24시간을 기준으로 잡았는데, 하루가 지나면 자동으로 키가 삭제되도록 했습니다. 일반적으로 결제 재시도는 몇 분 내에 이뤄지니까 충분한 시간이라고 판단했습니다.
또 하나 예상 못 한 문제는 키 충돌이었습니다. UUID를 쓰면 거의 발생하지 않지만, 만약 클라이언트가 잘못된 키 생성 로직을 쓰면 전혀 다른 요청인데도 같은 키가 생성될 수 있습니다. 그래서 키 생성 가이드를 명확히 문서화하고, 서버에서도 키 형식 검증 로직을 추가했습니다. 이런 방어 코드들이 쌓이다 보니 초기 예상보다 개발 기간이 2주 정도 더 걸렸습니다.
데이터 무결성 확보와 실전 적용 범위
결론적으로 Idempotency 전략을 도입하고 나서 중복 결제 문제는 완전히 사라졌습니다. 고객 컴플레인도 줄었고, 환불 처리에 들어가는 운영 비용도 크게 감소했습니다. 금융감독원 전자금융감독규정에 따르면 전자금융거래에서는 거래내용의 정확성과 안전성을 확보해야 하는데(출처: 금융감독원), 이 전략이 규정 준수에도 도움이 됐습니다.
다만 모든 API에 이걸 적용할 필요는 없다고 생각합니다. 조회성 API나 멱등성이 자연스럽게 보장되는 작업에까지 이 복잡한 구조를 강제하는 건 과도합니다. 결제, 주문 생성, 계좌 이체처럼 한 번만 실행돼야 하는 작업에만 선택적으로 적용했습니다. 실제로 제 팀에서는 전체 API 중 20% 정도만 Idempotency 키를 필수로 요구하고, 나머지는 선택 사항으로 뒀습니다.
또 클라이언트 구현 부담도 무시할 수 없습니다. 프론트엔드 개발자들이 매번 고유 키를 생성하고 관리해야 하는데, 이게 생각보다 신경 쓸 게 많습니다. 그래서 SDK를 만들어서 키 생성 로직을 라이브러리 안에 숨기고, 개발자는 단순히 메서드만 호출하면 되도록 추상화했습니다. 이렇게 하니 도입 저항이 훨씬 줄어들었습니다.
지금 돌이켜보면 Idempotency 전략은 구현 부담이 분명 존재하지만, 데이터 무결성이 중요한 시스템에서는 반드시 필요한 설계 요소라고 확신합니다. 초기 투자 비용은 있지만 장기적으로 보면 시스템 신뢰성과 운영 안정성을 크게 높여줍니다. 만약 지금 결제나 주문 시스템을 만들고 있다면, 처음부터 이 전략을 고려하는 걸 강력히 추천합니다. 나중에 추가하려면 기존 코드를 전면 수정해야 해서 훨씬 고통스럽습니다.
댓글
댓글 쓰기