[TIL] 한정판 드랍 커머스 Drops 도메인 성능 개선

김재진·2026년 5월 11일

내일배움캠프

목록 보기
69/70

1️⃣ Redis SETNX + TTL 기반 조회수 중복 방지

🚨 문제

같은 사용자가 동일한 드랍을 반복 조회할 때마다 DB 조회수가 증가해 과집계가 발생하고, DB UPDATE 부하도 높았습니다.

✅ 개선 내용

DropsQueryService에서 Redis SETNX로 중복 방문을 선점한 뒤 DB를 증가시킵니다.

📋 처리 흐름

1. 🪪 식별자 생성
   - 로그인 사용자    → 이메일
   - 비로그인 사용자  → IP + UserAgent 해시

2. 🔑 Redis SETNX: "drop:view:{dropId}:{identifier}" (TTL: 600초)
   - ✅ 성공 (첫 방문)  → DB 조회수 +1
   - ⛔ 실패 (재방문)   → 조회수 증가 없음

3. 💥 DB UPDATE 실패 시 → Redis 키 삭제 (보상 로직)

💡 rollbackViewKey 보상 로직으로 DB 실패 시 Redis 키를 롤백하여 다음 방문에 재시도가 가능합니다.

📈 효과

과집계 방지 및 불필요한 DB UPDATE 부하 감소


2️⃣ Redis Lua 스크립트 + 분산락 기반 재고 원자 선점

🚨 문제

드랍 오픈 시 동시 주문 요청이 몰릴 때 DB 행을 직접 UPDATE하는 방식은 락 경합으로 처리량이 제한되고 초과 차감 위험이 있었습니다.

✅ 개선 내용

DropsStockPreemptionService에서 Redis Lua 스크립트로 재고를 먼저 원자 선점한 뒤, 분산락으로 DB에 반영합니다.

⚙️ 재고 선점 스크립트 (RESERVE_SCRIPT)

local current = redis.call('get', KEYS[1])
if not current then return -2 end      -- 키 없음 (ACTIVE 전 요청 거부)
current = tonumber(current)
local quantity = tonumber(ARGV[1])
if current < quantity then return -1 end  -- 재고 부족
return redis.call('decrby', KEYS[1], quantity)

🔄 보상 스크립트 (INCREASE_IF_EXISTS_SCRIPT)

if redis.call('exists', KEYS[1]) == 1 then
  redis.call('incrby', KEYS[1], ARGV[1])
  return 1
end
return 0

🗂️ 주요 메서드

메서드역할
tryReserve주문 시 Redis 재고 원자 차감
compensateReservedStock트랜잭션 롤백 시 Redis 재고 복원
increaseStockAfterRestore주문 취소 후 재고 복원
preloadStockKey드랍 ACTIVE 전환 시 Redis에 재고 사전 적재

💡 TransactionSynchronizationManager를 통해 트랜잭션 롤백 시 자동으로 보상 로직이 실행됩니다.

📈 효과

동시 요청에 대한 원자 처리로 초과 차감 방지 및 DB 락 경합 제거


3️⃣ @EntityGraph를 통한 드랍 목록 N+1 개선

🚨 문제

드랍 목록 조회 시 연관 상품(Product)을 지연 로딩으로 가져와 드랍 수만큼 추가 쿼리가 발생했습니다.

✅ 개선 내용

DropsRepository의 목록 조회 메서드 전반에 @EntityGraph를 적용했습니다.

// DropsRepository.java
@EntityGraph(attributePaths = "product")
Page<Drops> findAllByStatusIn(List<DropsStatus> statuses, Pageable pageable);

💡 드랍 목록 / 판매자별 드랍 / 상품별 드랍 조회 모두 Drops + Product를 단일 쿼리로 처리합니다.

📈 효과

드랍 목록 조회 시 N+1 쿼리 제거


4️⃣ ShedLock 기반 분산 스케줄링

🚨 문제

단일 인스턴스 가정 하의 @Scheduled멀티 인스턴스 환경에서 모든 인스턴스가 동시에 드랍 상태 전이를 실행해 중복 처리가 발생했습니다.

✅ 개선 내용

DropsStatusScheduler에 ShedLock을 적용해 동시에 하나의 인스턴스만 스케줄러를 실행합니다.

@Scheduled(fixedDelayString = "${drops.scheduler.fixed-delay-millis:30000}")
@SchedulerLock(
    name = "dropsStatusScheduler_transitionDropsStatus",
    lockAtMostFor = "${drops.scheduler.lock-at-most-for:PT1M}",
    lockAtLeastFor = "${drops.scheduler.lock-at-least-for:PT1S}")
public void transitionDropsStatus() { ... }

⚙️ 락 설정

설정의미
⏱️ fixedDelay30초실행 주기
🔒 lockAtMostFor1분인스턴스 장애 시 락 자동 해제
🔓 lockAtLeastFor1초빠른 종료 후 다른 인스턴스 즉시 실행 방지

💡 락은 shedlock 테이블(JdbcTemplateLockProvider)로 관리하며, 상태 전이는 SCHEDULED → ACTIVE → FINISHED 두 단계로 수행됩니다.

📈 효과

멀티 인스턴스 환경에서 드랍 상태 전이 중복 실행 방지


📊 개선 요약

항목적용 기술주요 효과
👁️ 조회수 집계Redis SETNX + 보상 로직과집계 방지 및 DB UPDATE 부하 감소
📦 재고 선점Lua 스크립트 + 분산락동시 요청 원자 처리 및 초과 차감 방지
📋 드랍 목록 조회@EntityGraphN+1 쿼리 제거
🕐 상태 전이 스케줄링ShedLock멀티 인스턴스 중복 실행 방지
profile
개발공부 처음해보는 사람

0개의 댓글