ADR-003 소진 전 완충(FR-7)·소진 후 즉시 거부(FR-8) 분리 배치

  • 상태: 채택
  • 날짜: 2026-07-03
  • 근거: PRD FR-7·FR-8 / NFR / TDD “실패 경로·동시성·멱등”

맥락

PRD는 두 요구를 명시 분리한다 — FR-7(소진 폭주를 DB에 직결하지 않게 완충)과 FR-8(소진 초과 요청은 대기열에 넣지 말고 즉시 거부). 둘을 한 메커니즘에 뭉치면 소진 후 요청이 완충 자원(permit)을 낭비하거나, 완충이 소진 판정을 지연시킨다.

결정

두 관심사를 순서와 메커니즘으로 분리해 하나의 reserve() 원자 판정 안에 배치한다.

DropReservationStoreImpl.reserve() 내부 순서:

  1. FR-8 먼저 — Redis Lua decr-if-positive: remaining<quantitySoldOut 즉시 반환. DB·세마포어 미접근. 소진 후 초과 요청은 O(1)로 컷오프(완충 자원 미소모).
  2. FR-7 다음 — 인프로세스 세마포어: 입장 승인분(remaining>0)만 permit 획득 시도. timeout 내 미획득 시 Throttled(429). permit은 DB 주문 완료까지 보유(confirmSuccess/cancel에서 반납).

근거

  • 순서가 핵심: 소진 판정(Redis)을 완충(세마포어)보다 에 둬야 FR-8이 완충 자원을 낭비하지 않는다. 완충을 앞단 필터/인터셉터에 두면 소진분까지 permit을 소모 → 반패턴.
  • 메커니즘 분리 근거: 소진 판정은 글로벌 원자성이 필요(전 인스턴스 공유) → Redis. 완충은 인스턴스 DB 쓰기 상한 → 인프로세스 세마포어로 충분(지금 단일 인스턴스).
  • permit 생명주기가 DB 트랜잭션을 감싸야 하므로 reserve(획득)와 confirmSuccess/cancel(반납)로 브래킷.

대안 (미채택)

  • 완충을 presentation 필터/인터셉터로: 소진 판정보다 앞서 실행돼 FR-8이 permit 낭비. 도메인 게이트와 순서 역전.
  • 완충도 Redis 토큰 버킷으로: 단일 인스턴스에 과설계. 다중 인스턴스 확장 시점에 승격(Open Questions·Redis 후속 요구).
  • 대기열(큐)로 완충: 소진 후 요청까지 큐에 적재 → FR-8(“대기열에 넣지 않는다”) 위반.

영향

  • ReservationResult sealed에 SoldOut·Throttled·PerUserLimitExceeded·Admitted·AlreadyReserved 5분기.
  • LimitedDropDomainService가 결과별 예외 매핑(409/429/403).
  • 다중 인스턴스 확장 시 세마포어 → Redis 토큰 버킷 승격이 Redis 후속 요구.