1. 동시성 제어 방법
2. DataBase Lock 종류
3. DataBase Lock 구현
동시성 제어하는 여러 방법을 살펴보고 그중 DataBaseLevel 에 해당하는 DB Lock 의 종류와 사용방법을 정리한다.
출처는,
1. 동시성 제어 방법
Lock은 열쇠와 같다. CriticalSection (얻고자 하는 데이터 영역) 에 접근하기위에는 Lock 을 얻고(acquire) 데이터를 사용한 뒤에 Lock 을 다시 반납(Release) 한다. 이렇게 데이터에 대해 동시성 제어를 하여 race Condition(공유되는 데이터의 정합성이 깨지는 현상) 을 방지한다. 그리고 아래와 같이 여러 레벨에서 동시성 제어를 할 수 있다.
- Application Level : 코드 레벨에서의 제어
Synchronized 사용하기
- DataBase Level : Database 에서의 제어
Database 에서 제공하는 Lock 을 사용한다.
- Global Level(?) : 여러 방법이 있겠지만 Redis Distributed Lock 을 이용해 Global Lock 을 실행 시킬 수 있다.
Lettuce 라이브러리를 이용하여 setnx 혹은 Redission 의 Pub-sub 방식으로 구현 할 수 있다.
그리고 오늘은 이중에 DataBase Level 에서 사용할 수 있는 DataBase Lock 의 종류와 사용 방법을 정리해 본다.
2. DataBase Lock 종류
MySQL 에서는 아래의 3가지 Lock 을 이용하여 동시성 제어를 할 수 있다.
- Pessimistick Lock
- Optimistick Lock
- Named Lock
Pessimistick Lock
- 트랜잭션이 데이터를 얻고자 할때 Row 단위로 Lock 을 획득 할 수 있다.
- 특정 Row 를 update, delete 할 수 있으며 일반 Select 는 별다른 Lock 이 없기 때문에 조회는 가능하다.
Optimistick
- 데이터에 Lock 을 걸지 않고 버전을 이용해서 동시성 제어(race condition 을 방지) 하는 방법이다.
- 만약 두 트랜잭션이 같은 버전의 데이터를 읽을 때 그 중 하나의 트랜잭션이 데이터를 업데이트 하고 버전을 올린다. 그러면 나머지 트랜잭션이 데이터를 업데이트 하고자 할때 최초에 조회했던 데이터의 버전과 상이 하므로 업데이트 처리는 실패 하게 된다.
Named Lock
- 이름을 가진 metadata Lock 이다. 이름을 가진 Lock 을 획득 한 후 해제 할때까지 다른 세션은 동일한 데이터를 획득 할 수 없게 한다.
- 주의 할 점은 Transaction 이 종료 될때 Lock 이 자동으로 해제되지 않기 때문에 별도의 명령어로 해제를 수행해 주거나 선점 시간이 끝나야 해제 할 수 있다.
- 이것은 Pessimistic 락과 유사한데 Pessimistic Lock 은 row 나 테이블 단위로 걸지만 namedLock 은 메타데이터 Lock 을 하는 것이다.
3. DataBase Lock 구현
셋다 스프링 부트 프로젝트의 JPA Repostory 안에 Native 쿼리를 생성하여 구현하였다.
Pessimistick Lock
- PESSIMISTIC_WRITE 타입의 @Lock 애노테이션
아래와 같이 멀티 스레드 환경에서 여러 요청이 들어올때 ( 위 DAO 는 아래의 decrease 서비스 로직 안에 포함되어 있다.)
select 문에 for update 문이 함께 실행 되는 것을 확인 할 수 있다.
Otimistick Lock
- @Lock 추가, LockModeType.OPTIMISTIC
where 조건에 version 을 확인 하는 기능이 추가되는것을 로그로 확인 할수가 있다.
Optimistick Lock 은 별도의 Lock 을 갖지 않으므로 성능상 이점이 있을 수 있으나 업데이트가 시도 되었을때 실패 처리를 개발자가 직접 해야 한다는 단점이 있다.
NamedLock
Connection Pool 고갈 위험이 있기 때문에 별도의 JDBC 를 사용하는 것이 좋다.
직접 String 타입의 키를 전달하여 Lock 을 획득하고 반납하는 getLock 과 ReleaseLock dao 메소드를 구현한다.
그리고 임계영역 앞뒤에 getLock 과 releaseLock 을 사용한다.
NamedLock 은 주로 분산 Lock을 활용할 때 사용된다.
Pessimitic Lock 은 timeout 을 구현하기 어렵지만 NamedLock 은 손쉽게 구현이 가능하다. 다만 락 해제와 세션 관리를 잘 해야 해서 실제로 사용할때는 구현 방식이 어려울 수도 있다.