Skip to content

멱등키(Idempotency-Key) 처리 로직 개선 - 검증 순서, null 처리, 원자성 보강 #351

Description

@coderabbitai

📋 상위 이슈

#348

🔍 문제점

웨이팅 등록 서비스()의 멱등키 처리에서 다음 세 가지 문제가 발견되었습니다:

1. 멱등키 검증 순서 문제

  • 현재: 웨이팅 번호 생성(line 67) → Redis 시퀀스 증가 → 멱등키 확인(line 74)
  • 문제: 동일한 Idempotency-Key로 재요청 시에도 일일 시퀀스가 소모됨
  • 영향: 시퀀스 번호 낭비, 웨이팅 번호 불일치 가능성

2. Null/빈 멱등키 처리 누락

  • 현재: Idempotency-Key 헤더가 null이거나 빈 문자열일 때 검증 없이 Redis에 저장/조회
  • 문제: "null" 키로 Redis 오염, 모든 null 요청이 같은 엔트리 공유
  • 영향: 서로 다른 요청이 동일한 멱등 응답을 받을 수 있음

3. Race Condition (find→save 패턴 비원자성)

  • 현재: findByKey() → 없으면 → save() 패턴 사용
  • 문제: 두 연산 사이에 다른 요청이 끼어들 수 있음 (redisTemplate.set()은 SETNX가 아님)
  • 영향: 동시 요청 시 양쪽 모두 등록 로직 실행, 중복 웨이팅 생성 가능

✅ 해결 방안

1. 멱등키 검증 순서 변경

// BEFORE: line 67-74
String waitingNumber = generateWaitingNumber(storeId, timestamp);
String idempotentKey = httpServletRequest.getHeader("Idempotency-Key");
Optional<WaitingIdempotencyValue> existingIdempotencyValue = 
    waitingIdempotencyRepository.findByKey(idempotentKey);

// AFTER: 멱등키 검증을 먼저 수행
String idempotentKey = httpServletRequest.getHeader("Idempotency-Key");
Optional<WaitingIdempotencyValue> existingIdempotencyValue = 
    waitingIdempotencyRepository.findByKey(idempotentKey);
if (existingIdempotencyValue.isPresent()) {
    return existingIdempotencyValue.get().getResponse();
}
String waitingNumber = generateWaitingNumber(storeId, timestamp);

2. Null/빈 멱등키 검증 추가

String idempotentKey = httpServletRequest.getHeader("Idempotency-Key");
if (idempotentKey == null || idempotentKey.trim().isEmpty()) {
    throw new InvalidIdempotencyKeyException(); // 또는 적절한 처리
}

3. 원자적 연산으로 변경

  • Option A: Redis setIfAbsent() 사용
Boolean success = redisTemplate.opsForValue()
    .setIfAbsent(key, value, duration);
if (!success) {
    // 이미 존재하는 경우
    return redisTemplate.opsForValue().get(key);
}
  • Option B: Lua 스크립트 활용 (더 복잡한 로직에 적합)
local key = KEYS[1]
local value = ARGV[1]
local ttl = ARGV[2]
if redis.call('EXISTS', key) == 1 then
    return redis.call('GET', key)
else
    redis.call('SET', key, value, 'EX', ttl)
    return value
end

📍 영향 범위

  • 파일: nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java
  • 라인: 64-78, 112-114
  • 관련 클래스: WaitingIdempotencyRepository

🔗 참고 링크

👤 담당자

@Jjiggu

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions