애그리거트와 트랜잭션

한 주문 애그리거트에 대해 운영자는 배송 상태로 변경할 때 사용자는 배송지 주소를 변경하면 어떻게 될까? 아래 그림은 발생할 수 있는 다양한 경우 중에서 한 가지를 시간 순서로 표시한 것이다.

한 애그리거트를 두 사용자가 동시에 변경할 때 트랜잭션이 필요하다.

한 애그리거트를 두 사용자가 동시에 변경할 때 트랜잭션이 필요하다.

위 그림은 운영자와 고객이 동시에 한 주문 애그리거트를 수정하는 과정을 보여준다. 메모리 캐시를 사용하지 않을 경우 운영자 스레드와 고객 스레드는 같은 주문 애그리거트를 나타내는 다른 객체를 구하게 된다(트랜잭션마다 리포지터리는 새로운 애그리거트 객체를 생성한다).

운영자 스레드와 고객 스레드는 개념적으로 동일한 애그리거트이지만 물리적으로 서로 다른 애그리거트 객체를 사용한다. 때문에 운영자 스레드가 주문 애그리거트 객체를 배송 상태로 변경하더라도 고객 스레드가 사용하는 주문 애그리거트 객체에는 영향을 주지 않는다. 고객 스레드 입장에서 주문 애그리거트 객체는 아직 배송상태 전이므로 배송지 정보를 변경할 수 있다.

이 상황에서 두 스레드는 각각 트랜잭션을 커밋할 때 수정한 내용을 DBMS에 반영한다. 즉, 배송 상태로 바뀌고 배송지 정보도 바뀌게 된다. 이 순서의 문제점은 운영자는 기존 배송지 정보를 이용해서 배송 상태로 변경했는데 그 사이 고객은 배송지 정보를 변경했다는 점이다. 즉, 애그리거트의 일관성이 깨지는 것이다.

이런 문제가 발생하지 않도록 하려면 다음의 두 가지 중 하나를 해야 한다.

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

선점 잠금

선점 잠금(Pessimistic Lock)은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식이다. 아래의 그림은 선점 잠금의 동작 방식을 보여주고 있다.

선점 잠금의 동작 방식

선점 잠금의 동작 방식

위 그림에서 스레드 1이 선점 잠금 방식으로 애그리거트를 구한 뒤 이어서 스레드 2가 같은 애그리거트를 구하고 있는데, 이 경우 스레드 2는 스레드 1이 애그리거트에대한 잠금을 해제할 때까지 블로킹된다.

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

한 스레드가 애그리거트를 구하고 수정하는 동안 다른 스레드가 수정할 수 없으므로 동시에 애그리거트를 수정할 때 발생하는 데이터 충돌 문제를 해소할 수 있다. 앞서 배송지 정보 수정과 배송 상태 변경을 동시에 하는 문제에 선점 잠금을 적용하면 다음 그림과 같이 동작한다.

선점 잠금을 이용해서 트랜잭션 충돌 문제를 해결한다

선점 잠금을 이용해서 트랜잭션 충돌 문제를 해결한다

운영자 스레드가 먼저 선점 잠금 방식으로 주문 애그리거트를 구한 경우 운영자 스레드가 잠금을 해제할 때까지 고객 스레드는 대기 상태가 된다. 운영자 스레드가 배송 상태로 변경한 뒤 트랜잭션을 커밋하면 잠금을 해제한다. 잠금이 해제된 시점에 고객 스레드가 구하는 주문 애그리거트는 운영자 스레드가 수정한 배송 상태의 주문 애그리거트이다. 배송 상태이므로 주문 애그리거트는 배송지 변경 시 에러를 발생하고 트랜잭션은 실패하게 된다. 이 시점에 고객은 '이미 배송이 시작되어 배송지를 변경할 수 없습니다' 와 같은 문구를 보게 될 것이다.