비관적 잠금(선점 잠금, Pessimistic Lock)과 낙관적 잠금(비선점 잠금, Optimistic Lock)

들어가며

 

최근 DDD Start! 라는 DDD 관련 서적을 읽다가 비관적, 낙관적 잠금에 대한 내용이 나왔다. 애그리거트를 수정, 조회시 멀티스레드 환경에서 발생되는 문제를 다루는 내용이다. 해당 문제는 비관적 잠금, 낙관적 잠금을 통해 예방할 수 있는데 비관적, 낙관적 잠금은 DDD에 국한된 내용이 아니라서 DDD 카테고리가 아닌 다른 카테고리에 기록하면 좋을것 같아 포스팅하고자 한다.

 

 

멀티스레드 환경에서 어떤 문제가 생기길래?

 

DDD Start! 서적에 나온 예시를 그대로 들면 좋을것 같다. 예를 들면 온라인 쇼핑몰 서비스에서 고객이 주문한 상품의 배송지를 변경하는 스레드와 관리자 페이지에서 운영자가 해당 고객이 주문한 상품의 배송상태를 변경하는 스레드가 있다고 가정하자.

 

 

1) 운영자는 배송상태를 변경하기 위해 주문정보를 조회한다.

2) 고객은 배송지를 변경하기 위해 주문정보를 조회한다.

3) 운영자는 배송상태를 배송중으로 변경한다.

4) 고객이 배송지 변경한다.(그러나 배송중인 상품은 배송지를 변경할 수 없는 정책이 존재)

5) 운영자의 행동이 DB에 반영된다.

6) 고객의 행동이 DB에 반영된다.

 

위와 같은 시나리오는 배송중인 상품의 배송지가 변경되는 이슈가 생긴다.

이 문제를 해결하기 위해서는 DBMS가 지원하는 트랙잭션과 함께 비즈니스 로직에도 추가적인 트랜잭션 처리 기법이 필요하다.

 

 

비관적 잠금(선점 잠금선점 잠금, Pessimistic Lock)

 

비관적 잠금은 어떤 스레드가 주문 정보를 먼저 구했다면 주문 정보를 통해 어떠한 기능의 수행이 끝나기 전까지는 다른 스레드들이 주문정보를 구하지 못하도록 막는, 잠그는 방식이다. 다음과 같이 말이다.

 

비관적 잠금은 보통 DBMS가 제공하는 for update와 같은 쿼리를 사용해서 구현할 수 있다.

그리고 JPA의 경우 find()메서드에 LockModeType.PESSIMISTIC_WRITE 값을 전달하여 구현할 수 있다.

 

Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE);

 

 

비관적 잠금과 교착 상태

 

비관적 잠금을 사용할 때 교착상태가 발생할 수 있다.

 

1. 스레드 1: A 정보를 구하고 잠금

2. 스레드 2: B 정보를 구하고 잠금

3. 스레드 1: B 정보를 구하고자할 때 블로킹

4. 스레드 2: A 정보를 구하고자할 때 블로킹

 

위의 시나리오를 수행하면 두 스레드 모두 영원히 작업을 끝낼 수 없는 교착상태(Dead lock)에 빠지게 된다.

이런 문제가 발생하지 않도록 하려면 잠금을 시도할 때 최대 잠금 시간을 지정하면된다.

 

 

낙관적 잠금(비선점 잠금, Optimistic Lock)

 

낙관적 잠금은 비관적 잠금과는 달리 실제 잠금(선점)을 하지 않는다.  다음그림을 보자.

 

 

1) 운영자는 배송상태를 변경하기 위해 version이 1인 주문정보를 조회한다.

2) 고객은 배송지를 변경하기 위해 version이 1인 주문정보를 조회한다.

3) 운영자는 배송상태를 배송중으로 변경한다.

4) 고객은 배송지를 변경한다.

5) 운영자의 행동이 DB에 반영됨과 함께 version이 2로 업데이트 된다.

6) 고객의 행동이 DB에 반영됨과 함께 version 1 정보를 version 2로 업데이트 할 때 이미 DB엔 주문 정보 version이 2이므로 누군가 수정했다고 판단하여 트랜잭션 커밋이 실패된다.

 

즉, 주문 정보에 version 프로퍼티를 포함시켜 선점(잠금) 없이 트랜잭션 충돌을 방지 할 수 있다.

 

JPA는 낙관적 잠금을 지원하기 때문에 엔티티에 version으로 사용할 매핑되는 컬럼 프로퍼티에 @Version 어노테이션을 명시하면 구현이 가능하다.

 

@Entity
@Table(name = "order")
public class Order {
    ...
    
    @Version
    private long version;
    
    ...
}

 

추가로 JPA가 UPDATE 쿼리 수행시 자동으로 version을 증가시켜주기 때문에 비즈니스 로직에서 낙관점 잠금을 따로 처리할 필요 없다. 

'🍃 Spring' 카테고리의 다른 글

@Transactional 동작 원리  (0) 2021.05.26
AOP를 통해 Custom Annotation 처리하기  (1) 2020.04.15