Skip to content

[refactor][queue-service] enqueue/delete 로직 원자성 개선 (Lua Script 통합) #11

Description

@coderabbitai

📌 개요

현재 enqueuedelete 가 다단계 처리되어 race condition 윈도우가 존재한다.

enqueue

  • 1단계: setIfAbsent (역인덱스 락)
  • 2단계: MULTI/EXEC (Sorted Set + Hash)
  • 1단계 성공 후 2단계 실패 시 orphan 역인덱스 가능 (TTL 로 자동 정리되지만 일시적 불일치)

delete

  • Java 단 compare-and-delete 적용 (현재):
  String current = redisTemplate.opsForValue().get(userProgramKey);
  if (tokenIdStr.equals(current)) {
      redisTemplate.delete(userProgramKey);
  }
  • GET 과 DEL 이 별도 명령 → 사이의 race 윈도우 (1~2ms) 존재
  • 그 사이 역인덱스 변경 시 다른 토큰의 역인덱스를 잘못 삭제 가능 (Critical)

🎯 목표

  • enqueue 의 진짜 원자성 (중복 검사 + 저장 한 호출)
  • delete 의 race 윈도우 완전 제거
  • 네트워크 왕복 횟수 ↓ (성능 개선 부수 효과)

🧩 리팩토링 범위

  • 클래스 / 패키지 구조 개선
  • 메서드 분리 / 네이밍 개선 (Lua Script 정의 분리)
  • 중복 로직 제거 (다단계 → 단일 호출)
  • 트랜잭션 / 예외 처리 정리 (원자성 보장)

🏗️ 변경 설계

변경 전 구조

enqueue

setIfAbsent (1단계) → MULTI/EXEC (2단계)
→ 1단계 성공 + 2단계 실패 시 orphan 역인덱스

delete

GET → 검사 → DEL (Java 코드)
→ GET 과 DEL 사이 race 가능

변경 후 구조 — Lua Script

enqueue.lua

local userProgramKey = KEYS[1]
local programKey = KEYS[2]
local tokenKey = KEYS[3]
local tokenId = ARGV[1]
local score = ARGV[2]
local ttl = tonumber(ARGV[3])

if redis.call('EXISTS', userProgramKey) == 1 then
    return {err = "DUPLICATE"}
end

redis.call('SET', userProgramKey, tokenId, 'EX', ttl)
redis.call('ZADD', programKey, score, tokenId)
redis.call('HMSET', tokenKey, ...)
redis.call('EXPIRE', tokenKey, ttl)

return {ok = "OK"}

delete.lua (compare-and-delete)

local current = redis.call('GET', KEYS[1])
if current == ARGV[1] then
    redis.call('DEL', KEYS[1])
end
redis.call('ZREM', KEYS[2], ARGV[1])
redis.call('DEL', KEYS[3])

영향 받는 기능

  • enqueue() — Lua Script 로 통합
  • delete() — Lua Script 로 통합
  • 기존 setIfAbsent + MULTI/EXEC 로직 제거

⚠️ 주의 사항

  • 기능 동작은 기존과 동일 (원자성 강화만)
  • API 스펙 변경 없음
  • DuplicateTokenException 던지는 방식 동일 (Lua return 값으로 판별)
  • Lua Script 파일은 src/main/resources/scripts/ 등에 분리 관리

🧪 검증 방법

  • 기존 테스트 통과 여부 확인
  • 동시성 테스트 추가 (concurrent enqueue, race delete)
  • Lua Script 단위 테스트 (Testcontainers Redis 활용)

🔗 참고

Metadata

Metadata

Assignees

Labels

No fields configured for Feature.

Projects

Status
Done

Relationships

None yet

Development

No branches or pull requests

Issue actions