꾸준하게
재고 관리 순서에 따른 동시성 제어 전략 본문
현재 Buy Now 클릭 → 주문 생성 → 재고 감소 의 흐름으로 인해,
결제 창에서 취소하면 재고 복구가 안되는 현상이 발생하고 있습니다.
재고 감소를 어느 시점에 하냐에 따라 로직이 변경될 수 있습니다.
저는 현재 테스트를 위해 결제 창을 껐다 켰다 하는데 재고는 계속 줄어들기에
결제 완료를 하면 재고를 감소하는 방향으로 수정하였습니다.
하지만 그 전에 이렇게 하면 생길 문제를 먼저 말씀드리겠습니다.
Overselling 현상으로 예를 들어 재고 1개, 동시 결제하는 사람이 2명이면
상대적으로 늦은 사람은 0 -> -1로 재고소진하게 됩니다.
해결 방안
낙관적 락 @Version
OptimisticLockException을 통해 두번째 요청을 눌렀을 때는 이미 소진되었다는 문구를 띄워주는 방식으로 하면 좋을 것 같습니다.

여기서 A 유저가 결제 완료후 재고가 1 -> 0 이 되었을 때
B유저는 그러면 결제 완료시 OptimisticLockException을 받게 됩니다.

하지만 현재 위와 같은 흐름이라 B유저는 돈이 빠져나갔지만 에러 발생으로 인해 정상적으로 주문이 되지 않을 수 있습니다.
이를 위해 낙관적 락 에러가 발생하면 자동 환불하는 방식으로 해결하였습니다.
try {
order.getProduct().decreaseStock(order.getCount());
} catch (OptimisticLockingFailureException e) {
// 재고 충돌 → 자동 결제 취소
portOneService.cancelPayment(impUid, "재고 소진으로 인한 자동 취소");
throw new StockExhaustedException("재고가 소진되어 결제가 자동 취소되었습니다.");
}
낙관적 락이 좋지 않은 경우
지금은 쇼핑몰에서 일반적인 제품을 구매하는 상황을 가정하고 있습니다.
하지만 한정판 1개 상품에 100명이 동시에 결제 시도를 하는 경우가 있다고 하면 오히려 지금 같은 플로우는 좋지 않을 수 있습니다.
99명의 예외 처리로 인한 환불 API를 동시에 콜하게 되기에 서버 부하와 PG사 환불 API가 폭주하는 일이 발생할 수 있습니다.
위와 같은 경우에는 결제 전에 미리 재고를 선점하는 방식이 유용합니다.
제가 생각한 방식은 다음과 같습니다.
- DB 비관적 락
- Redis DECR
- Skip Locked
상황 : 100개 동시 요청, 100개 초기 재고
| 1차 시도 | 2차 시도 | 3차 시도 | |
| DB 비관적 락 | 527ms | 442ms | 523ms |
| Redis DECR | 34ms | 24ms | 27ms |
| Skip Locked | 241ms | 227ms | 272ms |
(여기서 Redis DECR은 DB 업데이트 하는 로직 비동기로 뺀 제외한 순수 예상 성능입니다.)
Redis DECR이 속도 측면에서는 가장 빨랐습니다.
Skip Locked는 일반 비관적 락의 2배의 성능을 보이는 것을 알 수 있었습니다.
Redis DECR을 넣는건 좋지만 앞서 가정한대로 DB 업데이트를 하며 데이터 싱크가 맞는지 감시하는 관리포인트가 늘어나는 단점이 있습니다.
반면 Skip Locked는 단일 DB안에서 충분히 가능하기에 관리 측면에서는 편리하다는 장점이 있습니다.

다만 Skip Locked 로 정확한 개수를 세기는 어렵습니다. 공식문서에서 말하듯이 일관성 없는 보기를 반환하기에 정확히 남은 총 재고 수량을 얻기에는 제한되는 단점이 있습니다.
이런 경우는 남은 재고 수 실시간성을 포기하며 특정 시간마다 조회를 통해 갱신하는 방식을 생각할 수 있겠습니다.
'테크 > 개발' 카테고리의 다른 글
| 결제 시에 멱등성 보장 및 결제 불일치 상황 해결 (0) | 2026.02.04 |
|---|---|
| @Transactional은 어떻게 동작하는걸까? (0) | 2025.07.01 |
| 연관관계 조인 전략에 대하여 (0) | 2024.03.07 |
| [자바 ORM 표준 JPA 프로그래밍] 영속성 관리 (0) | 2023.09.10 |
| [Spring Boot] AccessToken, RefreshToken을 이용한 로그인 구현 (0) | 2023.07.22 |
