ADR-001 동시성 전략 = Redis 원자 카운터 입장 게이트 + DB @Version SSOT

  • 상태: 채택
  • 날짜: 2026-07-03
  • 근거: PRD FR-3·FR-4·FR-7·FR-8 / NFR(20000TPS·오버셀 0·P95 800ms·실패율 5%) / TDD “Possible Solutions”

맥락

한정판은 단일 stocks row(Stock.kt, product_id PK)에 전 트래픽이 집중된다. 20000TPS 스파이크에서 이 한 row의 재고 차감을 어떻게 동시성 제어할지 확정이 필요하다. 기존 자산: Stock.@Version 낙관적 락(Stock.kt:22), createPendingOrder 멱등(GoodsDomainService.kt#createPendingOrder), DistributedLock(Redis SET NX EX+Lua).

결정

Redis 원자 카운터를 입장 게이트로 DB 앞단에 두고, DB Stock.@Version을 최종 SSOT로 유지하는 심층 방어를 채택한다.

  1. Redis DECR(Lua decr-if-positive)가 재고 슬롯을 원자 선점·거부 (초과분 DB 미도달, FR-8).
  2. 애플리케이션 세마포어가 입장 승인분의 DB 동시 쓰기를 완충 (FR-7).
  3. 기존 createPendingOrder + Stock.@Version이 DB에서 최종 차감·물리적 오버셀 봉쇄 (FR-3, 무변경 재사용).

근거

방안20000TPS 단일 row 관점판정
낙관적 락 + @Retryable피크 충돌률≈100%, 재시도가 주문+결제 트랜잭션 전체 재실행 → 증폭 포화, P95·실패율 NFR 위반단독 미채택
비관적 락 / 원자 UPDATE WHERE qty>=N재시도 폭주는 없으나 전량 DB 단일 row 락 직렬화 → DB 병목, 소진 후에도 DB 도달(FR-8 미충족)폴백으로만
Redis 카운터 게이트 + DB @VersionRedis 단일 스레드 O(1) DECR로 DB 경합을 재고 수량 근처로 상한, @Version 무변경 재사용, 이중 방어로 오버셀 0 물리 보장채택
  • Alibaba/Tmall seckill(재고 Redis 카운터 선점)·Shopify flash sale(체크아웃 throttle) 정합.
  • Redis 게이트는 Stock.@Version대체하지 않고 앞단에 얹기만 한다 → FR-3(기존 파이프라인 재사용) 충족.

실패 경로 (오버셀 0 불변식)

  • Redis DECR 후 DB 실패 → cancel()로 슬롯 복원.
  • DB 커밋 후 후단 실패 → 최악 언더셀(안전), 리컨실리에이션 복원.
  • Redis 장애 → fail-open to DB(@Version+세마포어), 오버셀 0 유지·처리량만 감소, redis-degraded 경보.
  • 리컨실리에이션: Redis remaining + DB deducted > limitedQuantity 감지 → LimitedDropOversoldEvent(⑥, source=oversell severity=critical).

대안 (미채택)

  • Redis 단일 SSOT + DB 비동기: FR-3 위반(DB가 최종 SSOT), Redis 유실 리스크, 과설계.
  • 프론트 가상 대기실: PRD Non-Goals(별도 FE 과제).

영향

  • 신규 domain gateway DropReservationStore(SeatLockStore 선례) + Redis Lua 구현.
  • private-redis-implementer가 키/TTL/Lua 계약 후속 수행.
  • 20000TPS는 목표 도전 — 미달 시 도달 TPS·병목(세마포어/DB 락/Redis) 기록(PRD Success Metrics).