글을 작성하게 된 계기
MySQL Named Lock에 대해 정리하기 위해 글을 작성하게 되었습니다.
1. Named Lock
임의의 문자열 에 대한 잠금을 설정하는 락으로, 유저 레벨 락(User Level Lock) 으로도 불립니다. 이는 단순히 사용자가 지정한 문자열에 대해 획득하고 반납하는 잠금으로, 많은 레코드를 복잡한 조건으로 변경하는 트랜잭션에서 유용하게 사용됩니다. 이를통해 동일 데이터를 변경하거나 참조하는 프로그램끼리 분류해서 락을 걸고 쿼리를 실행해 해결할 수도 있습니다.
네임드 락은 레코드가 아닌 별도의 공간 에 락을 겁니다. 만약 특정 세션(A)이 락을 획득했을 때 다른 세션(B)이 락을 획득하기 위해 접근하면 먼저 락을 획득한 세션(A)이 작업을 끝낼때 까지 기다려야 합니다. MySQL 8.0 부터는 네임드락을 중첩해서 사용할 수 있고 동시에 모두 해제하는 기능이 추가되었습니다.
2. 구현
스프링에서의 구현은 아래와 같습니다. getLock( ) 메서드를 통해 락을 획득한 후 비즈니스 로직을 수행하고 락을 해제하는 것입니다. 자동으로 락을 해제해주지 않기 때문에 개발자가 락을 직접 해제해 주어야 합니다.
1
2
3
4
5
6
7
8
public interface LockRepository extends JpaRepository<Stock, Long> {
@Query(value = "SELECT get_lock(:productId, 3000)", nativeQuery = true)
void getLockByProductId(@Param("productId") String productId);
@Query(value = "SELECT release_lock(:productId)", nativeQuery = true)
void releaseLockByProductId(@Param("productId") String productId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class NamedLockStockFacade {
private final LockRepository lockRepository;
private final StockService stockService;
public NamedLockStockFacade(LockRepository lockRepository, StockService stockService) {
this.lockRepository = lockRepository;
this.stockService = stockService;
}
@Transactional
public void decrease(Long productId, Long quantity) {
try {
lockRepository.getLockByProductId(productId.toString());
stockService.decrease(productId, quantity);
} finally {
lockRepository.releaseLockByProductId(productId.toString());
}
}
}
실제로 사용할 때는 DataSource를 분리해서 사용하는 것이 좋은데, 즉, 락을 획득하는 별도의 데이터베이스 를 두는 것을 말합니다. 기존 데이터베이스를 사용하면서 쓰기/읽기까지 하면서 락을 걸기 때문에 커넥션 풀 부족으로 다른 서비스에 영향을 줄 수도 있기 때문입니다.
1
2
3
4
5
6
7
8
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/{DATABASE_SCHEMA}
username: ${USERNAME}
password: ${PASSWORD}
hikari:
# 커넥션 풀은 한정 돼 있습니다.
maximum-pool-size: 200
3. 분산락
이를 활용해 분산 락을 구현할 수 있는데요, 분산락의 정의는 아래와 같습니다.
락 획득/해제에 MySQL을 사용하는 것입니다. 이는 Primary 데이터베이스 일 수도 있고, 별도의 락을 위한 데이터베이스일 수도 있는데, 별도의 데이터베이스를 사용한다면 추가적인 관리 비용이 발생할 수 있습니다. 이를통해 멀티서버 환경에서도 동시성 이슈를 안전하게 해결할 수 있습니다.
이 과정에서 들었던 의문은 일반적으로 데이터를 조회해오는 과정에서, 즉 데이터베이스와 통신하는 과정에서 많은 비용이 발생하게 되는데 과연
관계형 데이터베이스로 락을 걸었을 경우 어느 정도의 성능이 나오는지대해 확인해보지 못했습니다. 이 부분에 대해서는 학습을 한 후 추가적으로 글을 보강해야 할 것 같습니다.
4. 정리
MySQL에서 제공하는 NamedLock을 통해 동시성 문제를 해결할 수 있으며, 더 나아가 멀티 서버 환경에서도 이를 활용할 수 있습니다.

