Published on

도메인 주도 개발 시작하기 Chap8

Authors
  • avatar
    Name
    ywj9811
    Twitter

애그리거트 트랜잭션 관리

애그리거트와 트랜잭션

한 주문 애그리거트에 대해 운영자는 배송 상태로 변경할 때 사용자는 배송지 주소를 변경한다면?

Untitled

이러한 상황 에서는 두 스레드가 각각 트랜잭션을 커밋할 때 수정한 내용을 DB에 반영한다.

즉, 애그리거트의 일관성이 깨진다.

이러한 문제를 방지하기 위해서는 두 가지 중 한가지를 해야 한다.

  1. 운영자가 배송지 정보를 조회하고 상태를 변경하는 동안, 고객이 애그리거트를 수정하지 못하게 한다.
  2. 운영자가 배송지 정보를 조회한 이후 고객이 정보를 변경하면, 운영자가 애그리거트를 다시 조회한 뒤 수정하도록 한다.

이 두가지는 애그리거트 자체의 트랜잭션과 관련이 있으며 DBMS가 지원하는 트랜잭션과 함께 애그리거트를 위한 추가적인 트랜잭션 기법이 필요한데, 이때 사용할 수 있는 대표적인 처리 방식은 선점 잠금(Pessimistic) 과 비선점 잠금 (Optimistic) 두가지 방식이 있다.

선점 잠금

Untitled

이러한 방식으로 동작하는 것이다.

즉, 운영자 스레드가 먼저 선점 잠금 방식으로 주문 애그리거트를 구하면 운영자 스레드가 잠금을 해제할 때 까지 고객은 대기 상태가 된다.

운영자 스레드가 변경완료 한 뒤 커밋을 하면 잠금이 해제되고, 이때 고객 스레드가 구하는 주문 애그리거트는 운영자 스레드가 수정한 주문 애그리거트로, 이미 배송이 시작되어 배송지를 변경할 수 없다와 같은 안내 문구를 보개될 것이다.

선점 잠금은 보통 DBMS가 제공하는 행 단위 잠금을 사용해서 구현하는데, 오라클을 비롯한 다수의 DBMS가 for update와 같은 쿼리를 통해 특정 레코드에 한 커넥션만 접근할 수 있는 잠금 장치를 제공한다.

SpringDataJPA에서는 @Lock(LockModeType.PESSIMISTIC_WRITE) 를 통해 사용할 수 있다.

선점 잠금과 교착 상태

하지만, 이때 잠금 순서에 따른 DeadLock 이 발생하지 않도록 주의해야 한다.

  1. 스레드 1 : A 애그리거트에 대한 선점 잠금 구함
  2. 스레드 2 : B 애그리거트에 대한 선점 잠금 구함
  3. 스레드 1 : B 애그리거트에 대한 선점 잠금 시도
  4. 스레드 2 : A 애그리거트에 대한 선점 잠금 시도

이러한 상황에서 스레드 1은 영원히 B 애그리거트에 대한 선점 잠금을 구할 수 없다.

마찬가지로 스레드 2 또한 진행할 수 없다.

이러한 선점 잠금에 따른 DeadLock 은 사용자가 많아질 때 발생할 수 있고, 많은 스레드가 휘말릴 수 있다.

이런 문제가 발생하지 않도록 하기 위해서는 잠금을 구할 때 최대 대기 시간을 지정해야 하는데, JPA에서는 최대 대기 시간을 지정할 수 있다.

SpringDataJpa에서는 @QueryHints 를 이용해 javax.persistence.lock.timeout 과 값을 주어 타임아웃 설정을 한다.

비선점 잠금

Untitled

비선점 잠금은 애그리거트에 버전으로 사용할 숫자 타입 프로퍼티를 추개서 사용하는 것으로, 애그리거트를 수정할 때 마다 버전으로 사용할 프로퍼티 값이 1씩 증가하는 방식을 사용한다.

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

만약 다른 트랜잭션이 수정하게 된다면, 현재 버전이 달라져 위의 쿼리가 실패하게 된다.

Untitled

이러한 방식으로 트랜잭션 충돌 방지를 수행할 수 있는데, 쿼리 실행 결과로 수정된 행의 개수가 0이면 이미 누간가 앞서 데이터를 수정했다는 것이다.

이는 트랜잭션이 충돌한 것으로 트랜잭션 종료 시점에 예외가 발생한다.

OptimisticLockingFailureException 이 발생하며, 이를 통해 충돌 여부를 파악할 수 있다.

강제 버전 증가

애그리거트에 애그리거트 루트 외에 다른 엔티티가 존재하는 경우 기능 수행 도중 루트가 아닌 다른 엔티티의 값만 변경된다고 해보자.

이 경우 JPA는 루트 엔티티의 버전 값을 증가시키지 않는다.

연관된 엔티티의 값이 변경된다고 해도 루트 엔티티 자체의 값은 바뀌지 않기 때문인데, 애그리거트 관점에서 본다면 문제가 도니다.

애그리거트의 구성요소중 일부 값이 바뀌면 논리적으로 그 애그리거트는 바뀐 것이기 때문이다.

JPA는 이런 문제를 처리할 수 있도록 LockModeType.OPTIMISITC_FORCE_INCREMET 를 사용할 수 있게 해주며 이는 상태 변경에 상관 없이 트랜잭션 종료 시점에 버전 값을 증가시키는 것이다.