Skip to content

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

@coderabbitai

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

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