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

kmb·2023년 6월 5일
0

DB

목록 보기
9/10
post-thumbnail

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

애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하지 못하게 막는 방식.

애그리거트 : 복잡한 도메인을 상위수준에서 하나의 군집으로 묶은것.

만약 스레드1에서 먼저 선점 잠금 방식으로 애그리거트를 구하고, 그 뒤에 스레드2가 같은 애그리거트를 구한다면
스레드2는 스레드1이 애그리거트에 대한 잠금을 해제할 때까지 블로킹(Blocking)된다.

스레드1이 애그리거트를 수정하고 트랜잭션 커밋하면 잠금이 해제되고, 대기하고 있던 스레드2가 애그리거트에 접근하게 된다.
이때 스레드2는 스레드1이 수정한 애그리거트의 내용을 보게된다.

선점 잠금은 DBMS가 제공하는 행단위 잠금을 사용해서 구현한다.
JPA의 경우 Entity Manager가 find( ) 메서드를 이용해서 LockModeType.PESSIMISTIC_WRITE 값을 전달하면 해당 엔티티와 매핑된 테이블을 이용해서 구현할 수 있다.

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

 
선점 잠금은 사용자의 수가 많을 경우 교착상태(dead lock)가 발생할 가능성이 높으므로 최대 대기시간을 지정해야한다.

Spring Data JPA의 경우 @Lock 어노테이션을 사용해서 잠금 모드를 지정할 수 있으며, @QueryHints 어노테이션을 사용해서 최대 대기시간을 지정할 수 있다.

public interface MemberRepository extends Repository<Member, MemberId> {
	
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @QueryHints({
    	@QueryHint(name = "javax.persistence.lock.timeout", value = "2000")
    })
}

 


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

실제로 잠금을 하지 않고, 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식.

비선점 잠금을 구현하기위해 애그리거트에 버전으로 사용할 숫자 타입 프로퍼티를 추가하고, 애그리거트를 수정할 때마다 버전으로 사용할 프로퍼티 값을 1씩 증가시킨다. 이때 쿼리는 아래와 같다.

UPDATE aggtable SET version = version + 1, colx = ?, coly = ?
WHERE aggid = ? and version = 현재버전

즉 수정할 애그리거트와 매핑된 테이블의 버전값과 현재 애그리거트의 버전값을 비교해서 동일한 경우에만 데이터를 수정한다.
수정에 성공하면 버전 값을 1 증가시킨다.

위 그림의 경우 스레드1과 스레드2가 동일 버전의 애그리거트를 읽어와 수정하는데 스레드1이 먼저 커밋을 성공해서 버전이 +1 되었다.
이후에 스레드2는 커밋을 시도하더라도 버전 값이 다르므로 수정에 실패한다.

JPA의 경우 @Version 어노테이션을 사용해서 매핑되는 테이블에 저장할 칼럼을 추가하면 된다.

@Entity
public Class Order {
	
    ...
    
    @Version
    private long version;
    
    ...
}

 

애그리거트 루트 외에 다른 엔티티의 값만 변경된 경우 JPA는 루트 엔티티의 버전 값을 증가시키지 않는 특징을 갖는다.

하지만 루트 엔티티의 값이 바뀌지 않았더라도 애그리거트의 구성요소중 일부 값이 바뀌면 논리적으로 그 애그리거트는 바뀐것이므로
루트 애그리거트의 버전값이 증가해야만 비선점 잠금이 올바르게 동작된다.

따라서 Spring Data JPA의 경우 @Lock 어노테이션 및 LockModeType.OPTIMISTIC_FORCE_INCREMENT 잠금 모드를 사용하면
해당 엔티티의 상태가 변경되었는지에 상관없이 트랜잭션 종료 시점에 버전 값 증가처리를 한다.

public interface MemberRepository extends Repository<Member, MemberId> {
	
    @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
}

 

출처

  • 도메인 주도 개발 시작하기 (책)
profile
꾸준하게

0개의 댓글