databasebackend
재고 차감 동시성, UPDATE 한 줄로 끝내기
재고 10개 남은 상품에 동시에 주문이 들어오면? 순진한 코드는 이렇게 생겼다.
var stock = stockRepository.findByProductId(productId); // 조회
if (stock.getQuantity() >= amount) { // 검사
stock.decrease(amount); // 차감
}
조회-검사-차감 사이에 다른 트랜잭션이 끼어들면 재고가 음수가 된다. 격리수준 READ COMMITTED에서는 막아주지 않는다.
제일 단순한 답: 조건부 UPDATE
락이니 버전이니 가기 전에, DB가 원자적으로 처리하게 하면 된다.
UPDATE stock
SET quantity = quantity - :amount
WHERE product_id = :productId
AND quantity >= :amount
@Modifying
@Query("update Stock s set s.quantity = s.quantity - :amount " +
"where s.productId = :productId and s.quantity >= :amount")
int decrease(Long productId, int amount);
// 호출부
if (stockRepository.decrease(productId, amount) == 0) {
throw new OutOfStockException();
}
UPDATE는 행 락을 잡고 실행되므로 두 요청이 동시에 와도 순차 처리된다. 갱신된 행 수(0 or 1)가 성공 여부다.
다른 선택지와 비교
- 비관적 락(SELECT FOR UPDATE): 차감 전에 검증 로직이 복잡하면 필요. 단순 차감엔 과함
- 낙관적 락(@Version): 충돌이 드물 때. 재고처럼 핫한 행은 재시도 폭풍이 난다
- Redis 분산락: DB 한 대로 되는 걸 굳이. 인프라가 늘어나는 만큼 장애 지점도 는다
핫스팟 행의 단순 증감이라면 조건부 UPDATE가 가장 싸고 가장 안전하다. 도구는 문제 크기에 맞게.
조회 1