프로젝트 깃허브 [링크]
주문생성
로직
- 주문할
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)
이 발생
지금까지 쇼핑몰 재고관리 로직에서 발생한 동시성문제 의
문제상황 파악 및 원인분석을 해보았습니다.
다음 글에서는 이러한 동시성문제를 어떻게 해결할 수 있을지 학습하여 정리해보겠습니다.
감사합니다.