point-live-young/decisions/ADR-010 상품 주문 시 재고 동시성 제어 방식 선택.md at main · f-lab-edu/point-live-yo
대규모 사용자와 대용량 트래픽을 안정적으로 처리하는 포인트 기반 이커머스. Contribute to f-lab-edu/point-live-young development by creating an account on GitHub.
github.com
1편과 2편에서는 포인트 도메인과 차감 정책을 말했다.
하지만 실제 주문 흐름에서 마주한 문제는 따로 있었다.
여러 사용자가 동시에 같은 상품을 주문하면 어떻게 되는가?
문제 상황: 재고는 공유 자원이다
이 서비스는 포인트로 상품을 구매하는 이커머스 기능을 제공한다.
하나의 상품은 하나의 재고 수량을 가진다.
이때 여러 사용자 동시에 주문 요청을 보낸다면
아래와 같은 상황이 발생할 수 있다.
- 현재 재고 : 1
- 동시에 50명의 사용자가 주문 요청
- 모든 요청이 "재고 확인 -> 차감"을 통과
그 결과, 재고가 0 아래로 내려가거나
1개뿐인 상품이 여러 명에게 판매되는 오버셀(oversell) 이 발생한다.
실제 동시성 테스트
이 문제가 단순한 가정이 아닌지 확인하기 위해
테스트를 수행했다.
가정
- 특정 상품에 대해 재고 수량만큼만 주문이 성공해야 한다.
테스트 조건
- 재고 1개
- 50명의 사용자가 동시에 주문
- 오직 1건만 성공해야 함
검증 방식
- 해당 테스트를 20회 반복
- 단 1회라도 오버셀이 발생하면 실패로 판단
그결과..

이 테스트를 통해
재고 차감 로직이 단순 조회 → 감소 방식일 경우
동시성 보장이 되지 않는다는 것을 확인했다.
고려한 대안들
재고 동시성 문제를 해결하기 위해 다음 네 가지 방식을 검토했다.
1️⃣ 비관적 락 (Pessimistic Lock)
DB 레벨에서 행 잠금을 사용하는 방식이다.
SELECT ... FOR UPDATE 를 통해 해당 상품 레코드를 잠그고
트랜잭션이 끝날 때까지 다른 요청은 대기하도록 한다.
장점
- 데이터 무결성 확실히 보장
- 구현이 비교적 단순
단점
- 잠금 대기 발생 가능
- 트래픽이 몰릴 경우 성능 저하 우려
- 데드락 가능성
2️⃣ 낙관적 락 (Optimistic Lock)
버전 번호를 두고 충돌 발생 시
예외를 던지고 재시도하는 방식이다.
장점
- 성능이 비교적 우수
- 데드락 위험 없음
단점
- 충돌이 빈번하면 재시도 비용 증가
- 구현 복잡도 상승
- 재시도 로직 필요
3️⃣ 원자적 연산 (Atomic Update)
ex)
UPDATE product
SET stock = stock - 1
WHERE id = ? AND stock > 0;
위 쿼리 처럼 조건부 업데이트를 활용해 한 번의 쿼리로 재고 감소를 처리한다.
장점
- 빠름
- 구현이 간단
단점
- 복잡한 비즈니스 로직 확장에 한계
- DB 종속적
4️⃣ Redis 기반 분산락
Redis를 활용해 락을 획득한 뒤 재고를 차감하는 방식.
장점
- 확장성
- 분산 환경에 적합
단점
- 인프라 추가 필요
- 구현 복잡도 증가
- 락 만료/복구 처리 고려 필요
최종 선택: 비관적 락
최종적으로 비관적 락(Pessimistic Lock) 을 채택했다.
재고 차감 시 아래와 같은 흐름으로 처리했다.
- SELECT ... FOR UPDATE 로 상품 행 잠금
- 재고 확인
- 재고 차감
- 트랜잭션 종료 시 락 해제
선택 이유
- 동시성 테스트에서 오버셀 방지가 확실히 보장됨
- 주문 도메인에서는 “재시도 가능성”보다 “무결성 보장”이 더 중요하다고 판단
- 현재 예상 트래픽 규모에서 성능 저하가 크지 않음
- 구현 복잡도가 낮아 빠르게 적용 가능
재고는 포인트와 다르게
잘못 차감되면 되돌리기 어려운 자원이기 때문에
보수적인 선택을 했다.
결과와 트레이드오프
장점
- 오버셀 완전 방지
- 코드 복잡도 증가 없음
- 트랜잭션 경계 내에서 명확하게 제어 가능
단점
- 동일 상품에 대한 동시 요청이 많을 경우 병목 가능성
- 잠금 대기로 인한 응답 지연 우려
추후 고려 사항
이후 트래픽이 증가할 경우 다음을 검토할 수 있다.
- 낙관적 락 + 재시도 전략 전환
- Redis 기반 분산락 도입
- 상품 단위로 접근 빈도가 높은 경우 캐싱 전략 도입
- CQRS 구조로 읽기/쓰기 분리
현재 선택은
“지금 상황에서 가장 단순하고 확실한 방법” 이다.
'나의 개발 이야기' 카테고리의 다른 글
| [Point-live-young (3)] 포인트 만료 전략 선택 – 배치인가, 조회 시 처리인가? (0) | 2026.02.11 |
|---|---|
| [Point-live-young (2)] 만료 우선 차감(Early Expiry First) 알고리즘 설계 (0) | 2026.02.10 |
| [Point-live-young (1)] 포인트 기반 결제 도메인 설계 (0) | 2026.02.10 |
| 개발자로서의 가치관 - 함께, 그리고 긍정적인 영향 (3) | 2025.06.09 |
| 개발자로의 전향 (2) - 개발은 삶과 닮아 있다 (0) | 2025.06.02 |