Pessimistic lock (비관적 락)
⇒ 자원을 요청하면 동시성 문제가 발생할 것을 예상하고 lock을 걸어버리는 비관적 락이다.
Optimistic lock (낙관적 락)
⇒ 자원에 락을 걸어 선점하기보단, 동시성 문제가 발생했을 때 감지하고 처리하는 낙관적 락이다.
Named lock
Spring data jpa를 활용하면 @Lock
을 통해 손쉽게 pessimistice lock을 구현할 수 있다.
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimisticLock(Long id);
}
Test가 성공적으로 실행되며, 실행 중 for update
라는 부분이 lock을 걸고 데이터를 가져오는 부분이다.
장점
단점
Optimistic lock을 활용하기 위해 Stock Entity
에 version이라는 attribute을 추가한다.
@Version
private Long version;
Spring Data JPA에서 제공하는 @Lock
을 통해 Optimistic lock을 구현한다.
@Lock(LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithOptimisticLock(Long id);
Optimistic lock은 실패했을 때 재시도 과정이 필요하므로 facade를 만들어 그곳에서 service layer의 함수를 호출한다.
@Component
public class OptimisticLockFacade {
private final OptimisticLockStockService optimisticLockStockService;
public OptimisticLockFacade(OptimisticLockStockService optimisticLockStockService) {
this.optimisticLockStockService = optimisticLockStockService;
}
public void decrease(Long id, Long quantity) throws InterruptedException {
while (true) {
try {
optimisticLockStockService.decrease(id, quantity);
break;
} catch (Exception e) {
Thread.sleep(50);
}
}
}
}
장점
단점
따라서 충돌의 발생 빈도에 따라 lock을 다르게 사용하는 것을 추천한다.
Named Lock은 이름을 가진 metadata lock으로 이름을 가진 lock을 획득한 후 해제할 때까지 다른 세션은 이 락을 획득할 수 없다.
트랜잭션이 종료될 때 lock이 자동으로 해제되지 않기 때문에 다음과 같은 상황에서 lock을 해제할 수 있다.
MySQL에서의 Named lock 사용 명령어는 다음과 같다.
get-lock
release-lock
강의에서는 편의를 위해 JPA의 Native Query를 사용하고 동일한 data source를 사용한다.
실제로 사용할 때에는 data source를 분리하는 것을 추천한다.
같은 데이터 소스를 사용하면 connection pool이 부족해져 다른 서비스에도 영향을 줄 수 있다.
lock을 획득하고 종료하는 LockRepository
를 정의한다.
public interface LockRepository extends JpaRepository<Stock, Long> {
@Query(value = "select get_lock(:key, 3000)", nativeQuery = true)
void getLock(String key);
@Query(value = "select release_lock(:key)", nativeQuery = true)
void releaseLock(String key);
}
StockService 로직을 활용하여 락을 획득하고 해제할 facade를 정의한다.
try {
lockRepository.getLock(id.toString());
stockService.decreaseStock(id, quantity);
} finally {
lockRepository.releaseLock(id.toString());
}
StockService의 재고 감소 로직은 facade의 transaction과 별도로 실행되어야 하므로 propagation을 변경한다.
// 부모(NamedLockStockFacade)의 transaction과 별도로 실행되어야 하므로 propagation 변경
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void decreaseStock(Long id, Long quantity) {
// Stock 조회
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
예제에서는 같은 data source를 활용하여 두 로직(재고 감소 & lock의 획득 및 해제)을 실행하므로 connection pool의 사이즈를 증가시킨다.
// application.yml 파일
spring:
datasource:
hikari:
maximum-pool-size: 40
Named lock은 주로 Distributed lock을 구현할 때에 사용된다.
장점
단점