프로젝트 깃허브 [링크]
주문생성 로직
- 주문할
Product List조회reduceProductsStock()을 통해 ->product.reduceStock()메서드를 호출하고더티체킹을 통해 재고차감Order생성OrderProduct생성
1.
line 90-> 2000개의 스레드환경 세팅
2.line 91-> 2000회 카운팅을 위한CountDownLatch
3.line 94, 105-> 스레드에게 작업부여
4.line 100, 111-> 스레드마다 작업종료후 카운트차감
5.line 115-> 2000회 카운팅이 종료된 이후에 테스트 검증로직이 작동할 수 있도록기다려!해주는 역할
- 100개의 스레드에서 동시에 1개씩 주문을 넣었을 때, 30~40개의
Order & OrderProduct레코드가 생성된다.- 심지어
Product.stock-> 상품재고는 10개정도밖에 차감되지 않는다.- 데드락이 발생한다.
간략한 정리
Order & OrderProduct레코드가 30~40개만 생성된 이유
- 요약 ->
Deadlock이 발생하고, DB에서 해당 트랜잭션들을 롤백시켜서!
.- 프로젝트에서 사용중인
Maria DB는InnoDB 스토리지 엔진을 사용한다.
해당 엔진은timeOut의 기본값이 50초(링크),대기중인 트랜잭션 수(기본값 200)로 데드락을 판단하여 개입한다 (하나씩 롤백하면서 해결).
- 상품 재고가 10개 내외만 차감된 이유
조회시점과update쿼리 트랜잭션 커밋시점의 차이로 인해
다른 트랜잭션의재고 update가 반영되지 않은 데이터를 기준으로더티체킹이 이루어졌기 때문.- 갱신 손실(Lost Update) 이라는 현상
결론부터 말하자면 더티체킹의 작동방식과 OrderProduct Insert 쿼리로 인한 공유락이 맞물려서 발생한 것이다. (정확히는 공유락과 배타락이 맞물린 것)
글 도입부에 첨부된 로직을 보면 상품 재고차감이 먼저 이루어지는 것처럼 보이지만, 실제로 더티체킹이 이루어지는 시점은 트랜잭션 커밋 직전이다.
이에 따라 트랜잭션 커밋이 진행되는 순서는
Order Insert -> OrderProduct Insert -> Product Update 순으로 진행된다.
공유락(Shared Lock)은 어디서 발생한 것인가?
-> MariaDB와 동일한 InnoDB 스토리지 엔진을 사용하는 MySQL DB의 자료를 확인해보았더니 ProductId를 FK로 가지는 OrderProduct Insert에서 공유락이 발생한 것을 확인할 수 있었다.
FOREIGN KEY제약 조건이 테이블에 정의된 경우 제약 조건을 확인해야 하는 모든 삽입, 업데이트 또는 삭제는 제약 조건을 확인하기 위해 확인하는 레코드에 공유 레코드 수준 잠금을 설정합니다 . InnoDB또한 제약 조건이 실패하는 경우 이러한 잠금을 설정합니다 [Ref 하이라이트 링크]

배타락(Exclusive Lock)을 요청한다. 서로가 끝나기만을 기다리는 상황이기에 결국 Inno DB가 개입해서 트랜잭션들을 롤백 시키면서 데드락을 해결한다.MySQL 공식문서[링크]에 따르면 대기목록 허용치를 초과한 상황에서
대기 목록 확인을 시도하는트랜잭션은 롤백되고, 트랜잭션을 하나씩 롤백해나가면서 데드락을 해결해나간다고 한다.
- 이런 전개로 30~40개 정도의
Order&OrderProduct Pair만 생성되었다고 보여진다.

다른 트랜잭션의 update 쿼리가 커밋되기 전에 상품을 조회해오고(100개)더티체킹을 통해 Update Query Commit 했기 때문이다. 조회시점과 Update 시점의 차이로 인해
조회해온 Product와 DB의 Product 간의데이터 정합성이 깨지게 되면서
갱신손실(Lost Update)이 발생
지금까지 쇼핑몰 재고관리 로직에서 발생한 동시성문제 의
문제상황 파악 및 원인분석을 해보았습니다.
다음 글에서는 이러한 동시성문제를 어떻게 해결할 수 있을지 학습하여 정리해보겠습니다.
감사합니다.