Spring Boot – 멱등성(Idempotency)과 멱등키 활용하기

By | 2026년 2월 23일
Table of Contents

Spring Boot – 멱등성(Idempotency)과 멱등키 활용하기

네트워크 오류로 인해 결제 요청이 두 번 가거나, 실수로 버튼을 여러 번 클릭해서 중복 주문이 발생한 적이 있나요? 이러한 문제를 우아하게 해결해 주는 핵심 기술이 바로 멱등키(Idempotency Key)입니다.

1. 멱등성(Idempotency)이란?

멱등성이란 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미합니다.

  • 수학적 예시: 어떤 수에 1을 곱하는 연산은 여러 번 반복해도 결과가 같습니다.
  • HTTP 메서드 예시: * GET, PUT, DELETE는 기본적으로 멱등성을 가집니다.
  • POST는 호출할 때마다 새로운 리소스를 생성할 수 있으므로 기본적으로 비멱등(Non-idempotent)합니다.

2. 멱등키(Idempotency Key)의 역할

POST와 같이 비멱등한 요청을 안전하게 처리하기 위해 클라이언트는 요청 헤더에 유니크한 값(UUID 등)을 담아 보냅니다. 서버는 이 키를 확인하여:

  1. 처음 본 키라면: 요청을 정상 처리하고 결과를 저장합니다.
  2. 이미 처리된 키라면: 실제 로직을 수행하지 않고 저장된 결과(응답)를 그대로 반환합니다.

3. Spring Boot에서 멱등키 구현하기

Spring Boot 환경에서 멱등키를 구현하는 가장 효율적인 방법은 RedisInterceptor를 활용하는 것입니다.

A. 기술 스택

  • Redis: 분산 환경에서 키를 빠르게 조회하고 만료 시간(TTL)을 설정하기에 적합합니다.
  • HandlerInterceptor: 컨트롤러에 진입하기 전 멱등키를 검사합니다.

B. 흐름도

  1. 클라이언트가 Idempotency-Key를 헤더에 담아 요청.
  2. 서버(Interceptor)가 Redis에서 해당 키 존재 여부 확인.
  3. Key 없음: Redis에 키 저장 (Status: PROCESSING) -> 비즈니스 로직 수행 -> 결과 저장 및 반환.
  4. Key 있음: * PROCESSING 중이면 "처리 중" 에러 반환.
    • 완료된 상태면 저장된 응답값 반환.

C. 주요 코드 예시

1. Redis 설정 및 로직 (Pseudo Code)

@Component
@RequiredArgsConstructor
public class IdempotencyService {
    private final RedisTemplate<String, Object> redisTemplate;

    public boolean isFirstRequest(String key) {
        // SETNX (Set if Not Exists)를 사용하여 원자적 체크
        return redisTemplate.opsForValue()
            .setIfAbsent(key, "PROCESSING", Duration.ofMinutes(10));
    }

    public void saveResponse(String key, Object response) {
        redisTemplate.opsForValue().set(key, response, Duration.ofMinutes(60));
    }
}

2. Interceptor 구현

@Component
@RequiredArgsConstructor
public class IdempotencyInterceptor implements HandlerInterceptor {
    private final IdempotencyService idempotencyService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String idempotencyKey = request.getHeader("Idempotency-Key");

        if (idempotencyKey == null) return true; // 필수가 아니라면 통과

        if (!idempotencyService.isFirstRequest(idempotencyKey)) {
            // 이미 존재하는 키라면 중복 요청 처리 (409 Conflict 또는 기존 응답 반환)
            response.setStatus(HttpServletResponse.SC_CONFLICT);
            return false;
        }
        return true;
    }
}

4. 도입 시 주의사항

  • 키의 범위: 유저 ID와 멱등키를 조합하여 키를 생성하는 것이 안전합니다. (다른 유저와 키가 겹칠 확률 방지)
  • 만료 시간(TTL): 멱등키를 무한히 저장할 수는 없으므로, 비즈니스 특성에 맞춰(예: 24시간) 적절한 유효 기간을 설정해야 합니다.
  • 성공/실패 케이스: 비즈니스 로직이 실패했을 경우, 멱등키를 삭제하여 재시도할 수 있게 할지 아니면 실패 기록을 남길지 정책을 정해야 합니다.

답글 남기기