주문·재고 / MariaDB에서 동일 옵션의 동시 예약 검증
재고 동시 예약 제어
MariaDB · 동시 요청 2건 중 성공 1건, 거절 1건
- 문제
- 같은 재고를 보고 두 요청이 모두 예약하면 초과 판매가 발생
- 선택
- 가용 수량 조건과 예약 갱신을 하나의 UPDATE로 처리
- 검증
- MariaDB: 동시 요청 2건 중 1건 성공·1건 거절, 최종 예약 1개
재고 예약 설계·검증 과정
원인과 제약
조회와 갱신이 분리되면 조회 시점의 수량을 갱신 시점에 보장할 수 없습니다. 주문의 예약 경쟁과, 관리자 상품 편집이 최신 재고를 덮어쓰는 충돌은 보호할 범위도 달랐습니다.
대안과 선택 이유
모든 요청에 같은 락을 걸지 않았습니다. 예약은 가용 수량 조건이 포함된 원자적 UPDATE로, 물리 재고 차감과 이력 기록은 비관적 락으로, 관리자 수정은 expectedVersion을 비교하는 낙관적 충돌 검사로 나눴습니다. 예약은 별도 조회 없이 수량 조건과 갱신을 묶고, 차감은 변경 전후 수량과 이력을 함께 보호하기 위해서입니다.
실제 구현
가용 재고 = 실물 − 예약 − 판매 보류입니다. 조건을 만족한 행 수가 요청한 옵션 수와 다르면 트랜잭션 전체를 롤백합니다. 여러 옵션은 ID를 정렬해 처리하고, 옵션 마스터와 inventory_option_balance의 version을 분리했습니다.
검증 시나리오와 결과
- MariaDB 통합 · 통과재고 1개에 동일 옵션 예약 요청 2건을 동시에 실행
- 두 스레드를 같은 시작 신호로 출발시키고 서비스의 독립 트랜잭션에서 각각 1개를 예약했습니다. 1건 성공·1건 재고 부족 거절, 최종 reserved_quantity = 1로 초과 예약이 없었습니다. 격리된 로컬 MariaDB 11.4 · 2026.09.20.
- H2 통합 · 통과재고 5개·1개인 두 옵션에 각각 2개 예약
- 한 옵션의 재고가 부족하면 전체 예약이 롤백되어, 두 옵션 모두 예약 수량 0개를 유지했습니다.
- H2 통합 · 통과주문 조회 뒤 상품이 판매 중지·숨김 상태로 변경
- 갱신 시점의 판매 조건으로 예약을 거절하고, 함께 요청한 다른 옵션의 예약도 롤백했습니다.
운영 보호 장치예약 가능한 수량을 DB의 갱신 조건으로 강제합니다. 여러 옵션을 함께 예약할 때 하나라도 조건을 충족하지 못하면 전체 예약을 롤백해, 일부 상품만 예약된 상태가 남지 않도록 처리합니다.
핵심 구현: 재고 예약 SQL
가용 수량과 판매 상태를 UPDATE 조건에 함께 둔 실제 예약 SQL입니다. 변경된 행이 1개가 아니면 서비스에서 예외를 발생시켜 예약을 확정하지 않습니다.
UPDATE inventory_option_balance
SET reserved_quantity = reserved_quantity + #{quantity},
version = version + 1
WHERE option_id = #{optionId}
AND stock_quantity - reserved_quantity - sales_hold_quantity >= #{quantity}
AND EXISTS (
SELECT 1
FROM product_option po
JOIN product p ON p.product_id = po.product_id
WHERE po.option_id = inventory_option_balance.option_id
AND po.deleted_at IS NULL
AND po.option_status = 'SELLABLE'
AND p.deleted_at IS NULL
AND p.product_status = 'ACTIVE'
)