6/27 ~ 7/11
동시성 (DB Lock)
Github
지난번에 커머스 플랫폼의 주문 플로우를 비슷하게 구현하였습니다.
해당 상점에서 다음과 같은 문제가 발생하였습니다.
1) 재고는 한정적인데 여러 사용자가 몰릴 경우 재고가 전부 소진되어도 주문이 되는 현상이 발생합니다.
해당 문제를 해결하면서 왜 이런 문제가 발생하였는지 고민해보세요.
기존에 작업했던 commerce-server에서 작업을 진행해주세요.
예전에 공유드린 커리큘럼에서 이 문제에 대한 내용이 나옵니다. 키워드를 찾아서 학습하세요.
제출 결과물
1) 코드를 작성한 GitHub Repository 주소
2) 이 문제 해결 경험을 적은 발표 자료
주의사항!
요구사항에 적히지 않은 문제에 대해서는 스스로 정의하고 분석을 진행해주세요.
재고는 한정적인데 여러 사용자가 몰릴 경우 재고가 전부 소진되어도 주문이 되는 현상
해결 방법
어떤 사용자가 데이터를 읽기/쓰기 할 때, Lock을 걸어 다른 사용자들은 데이터를 동시에 수정할 수 없게 한다.
낙관적 락의 흐름
데이터를 읽을 때 버전 번호를 함께 읽음
데이터를 변경할 때 읽어온 버전 번호가 현재 버전 번호와 일치하는지 확인
- 일치하면 데이터 업데이트, 버전 번호 증가
- 다르면 트랜잭션을 롤백하거나 다시 시도
재고가 4개 있는 상품 1개 주문이 동시에 10개의 주문이 들어왔을 때,
첫번째 주문만 수락되고 나머지 9개의 주문은 재고 부족으로 실패한다.
낙관적 락이 적합한 환경
비관적 베타 락의 흐름
트랜잭션이 시작되면 데이터에 대한 락 설정
락이 걸린 상태에서 데이터를 읽고 변경 작업 수행
작업이 완료되면 락 해제
재고가 4개 있는 상품 1개 주문이 동시에 10개의 주문이 들어왔을 때,
앞 4개의 주문은 수락되고 나머지 6개의 주문은 재고 부족으로 실패한다.
비관적 락이 적합한 환경
동시에 여러 사용자가 재고에 접근하여 수정하려 하기 때문에 비관적 락을 사용
베타 락을 사용하면 락을 소유한 트랜잭션이 데이터를 독점하여 읽기, 쓰기가 가능
데이터의 무결성과 일관성을 보장하고, 사용자 경험을 향상시킴
두 개 이상의 트랜잭션이 서로 상대방이 점유하는 자원을 기다려 무한 대기하는 상황
트랜잭션 A: 테이블1의 1번 데이터에 lock을 획득
트랜잭션 B: 테이블2의 1번 데이터에 lock을 획득
트랜잭션 A: 테이블2의 1번 데이터에 lock 획득 시도(실패 - 대기)
트랜잭션 B: 테이블1의 1번 데이터에 lock 획득 시도(실패 - 대기)
데드락 예방을 위해 보통 타임아웃을 설정 - 일정 시간이 지나도 락을 획득하지 못하면 트랜잭션 중단
현재 프로젝트에는 단일 자원에만 락을 사용하기 때문에 데드락 발생 가능성은 낮음
Lock을 걸지 않은 상태, 비관적 락과 베타 락을 건 상태 비교

현재 열라면 재고는 10개다.
curl
계획했을 당시에는 테스트 코드로 동시 요청을 보내려고 했는데, curl을 사용해서 더 쉽게 동시에 API 요청을 보낼 수 있다는 것을 알게 되어 curl을 사용했다.
아무것도 하지 않은 상태에서 열라면 5개입 상품 2개 주문을 동시에 다섯번 요청해봤다.
재고는 10개 이기 때문에 첫 번째 주문만 받을 수 있다.
curl -d '{ "contact": 1012345678, "address": "엔터팰리스3차", "orderItemList": [ { "itemId": 1, "quantity": 2 } ] }' -H "Content-Type: application/json" -X POST localhost:8090/v1/orders & \
curl -d '{ "contact": 1012345678, "address": "엔터팰리스3차", "orderItemList": [ { "itemId": 1, "quantity": 2 } ] }' -H "Content-Type: application/json" -X POST localhost:8090/v1/orders & \
curl -d '{ "contact": 1012345678, "address": "엔터팰리스3차", "orderItemList": [ { "itemId": 1, "quantity": 2 } ] }' -H "Content-Type: application/json" -X POST localhost:8090/v1/orders & \
curl -d '{ "contact": 1012345678, "address": "엔터팰리스3차", "orderItemList": [ { "itemId": 1, "quantity": 2 } ] }' -H "Content-Type: application/json" -X POST localhost:8090/v1/orders & \
curl -d '{ "contact": 1012345678, "address": "엔터팰리스3차", "orderItemList": [ { "itemId": 1, "quantity": 2 } ] }' -H "Content-Type: application/json" -X POST localhost:8090/v1/orders &

하지만 실행 결과 5개 요청이 모두 수락되었다.


@Lock 어노테이션 사용setLockMode() 메서드 사용JPA Lock 옵션
| 락 모드 | 타입 | 설명 |
|---|---|---|
| 낙관적 락 | OPTIMISTIC | 낙관적 Lock 사용 |
| 낙관적 락 | OPTIMISTIC_FORCE_INCREMENT | 낙관적 Lock + 버전 정보 강제 증가 |
| 비관적 락 | PESSIMISTIC_READ | 비관적 Lock, 읽기 Lock 사용 |
| 비관적 락 | PESSIMISTIC_WRITE | 비관적 Lock, 쓰기 Lock 사용 |
| 비관적 락 | PESSIMISTIC_FORCE_INCREMENT | 비관적 Lock + 버전 정보 강제 증가 |
| 기타 | NONE | 엔티티에 @Version이 있으면 낙관적 Lock을 적용함 |
| 기타 | READ | 하위 호환을 위한 것으로 OPTIMISTIC와 같음 |
| 기타 | WRITE | 하위 호환을 위한 것으로 OPTIMISTIC_FORCE_INVREMENT와 같음 |
QueryDSL에 setLockMode() 메서드를 추가하면 된다.
public Product findProductWithLock(Long id) {
return queryFactory.selectFrom(product)
.where(product.id.eq(id))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
}
.setLockMode(LockModeType.PESSIMISTIC_WRITE)

이번엔 재고를 20개로 두었다. 2개의 주문만 수락되어야 한다.


재고 부족 예외가 3번 발생했다.


미리 생성해둔 주문 5개를 제외하고 2개의 주문이 새로 생성되었다.
JPA Hint를 통해 QueryDSL에서 타임아웃을 설정할 수 있다.
public Product findProductWithLock(Long id) {
return queryFactory.selectFrom(product)
.where(product.id.eq(id)
.and(product.deletedAt.isNull()))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 10000) // 10초
.fetchOne();
}
setHint() 메서드를 사용해 락 타임아웃을 설정한다.
다음공부
동시성과 병렬성
저 방법으로 타임아웃 실행 안됨 이거 적용했더니 됐음
참고
낙관 락 vs 비관 락 실무에서 구체적인 예시를 들어주실 수 있을까요?
상품 주문 동시성 문제 해결하기 - DeadLock, 낙관적 락(Optimistic Lock) & 비관적 락(Pessimistic Lock)
동시성 문제 해결하기 V2 - 비관적 락(Pessimistic Lock)
비관적 락은 무엇이고 왜/언제 사용할까?
[JPA] 비관적락을 사용해 동시성 문제 해결하기 (curl command로 동시요청)
[데이터베이스] MySQL의 Lock과 트랜잭션 모델
🐧 CURL 명령어 사용법 💯 완전 총정리
공유락(Shared Lock) & 배타락(Exclusive Lock)
재고시스템으로 알아보는 동시성이슈 해결방법
Spring 동시성 문제(데이터 정합성) 뿌셔보기~
JPA PESSIMISTIC_READ does not time out in the specified period