Chapter 08. 애그리거트 트랜잭션 관리

beanii·2023년 4월 3일
0

DDD Study

목록 보기
8/11
post-thumbnail

8.1 애그리거트와 트랜잭션


  • 애그리거트에 대해 사용할 수 있는 대표적인 트랜잭션 처리 방식
    • 선점 잠금
    • 비선점 잠금

8.2 선점 잠금


  • 선점 잠금(Pessimistic Lock)은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하지 못하게 막는 방식
  • 먼저 사용하고 있는 스레드가 애그리거트에 대한 잠금을 해제할 때까지 다른 스레드는 블로킹(Blocking)됨
  • 사용한 스레드가 애그리거트 수정 후 트랜잭션 커밋하면 잠금 해제

  • 선점잠금은 보통 DBMS가 제공하는 행단위 잠금 사용해서 구현
  • JPA EntityManager는 LockModeType을 인자로 받는 find() 메서드 제공
  • LockModeType.PESSIMISTIC_WRITE를 전달
  • 스프링 데이터 JPA는 @Lock 애너테이션 사용해서 잠금 모드 지정

8.2.1 선점 잠금과 교착 상태

  • 선점 잠금 기능을 사용할 때는 잠금 순서에 따른 교착 상태(deadlock)가 발생하지 않도록 주의
  • 선점 잠금에 따른 교착 상태는 상대적으로 사용자 수가 많을 때 발생할 가능성 높음
  • 사용자 수가 많아지면 교착 상태에 빠지는 스레드 더 빠르게 증가

  • 잠금 구할 때 최대 대기 시간 지정해서 이런 문제 발생 방지
  • JPA에서는 힌트 사용
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.lock.timeout", 2000); //밀리초 단위로 지정
Order order = entityManager.find(
	Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE, hints);
  • 스프링 데이터 JPA는 @QueryHints애너테이션 사용해서 쿼리 힌트 지정
@QueryHints({@QueryHints(name = "javax.persistence.lock.timeout", value = "2000"})

8.3 비선점 잠금


  • 선점 잠금이 강력해 보이지만 선점 잠금만으로 모든 트랜잭션 충동 문제 해결 되는 것은 아님

  • 비선점 잠금
    • 동시에 접근하는 것을 막는 대신 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식
    • 애그리거트에 버전으로 사용할 숫자 타입 프로퍼티 추가해야 함
    • 애그리거트 수정할 때마다 버전으로 사용할 프로퍼티 값 1씩 증가
UPDATE aggtable SET version = version + 1, colx = ?, coly = ?
WHERE aggid = ? and version = 현재버전
  • 쿼리 분석
    • 수정할 에그리거트와 매핑되는 테이블의 버전 값이 현재 애그리거트의 버전과 동일한 경우에만 데이터 수정
    • 수정에 성공하면 버전 값 1 증가

  • JPA는 버전을 이용한 비선점 잠금 기능 지원
    • 버전으로 사용할 필드에 @Version 애너테이션 붙이고 매핑되는 테이블에 버전을 저장할 칼럼 추가
    • 엔티티가 변경되어 UPDATE 쿼리 실행할 때 @Version 명시한 필드 이용해서 비선점 잠금 퉈리 실행
    • 응용 서비스는 버전에 대해 알 필요 없음. 리포지터리에서 필요한 애그리거트 구하고 알맞은 기능만 실행하면 됨. 애그리거트 데이터 변경되면 JPA는 트랜잭션 종료 시점에 비선점 잠금을 위한 쿼리 실행
    • 비선점 잠금을 위한 쿼리 실행할 때 쿼리 실행 결과로 수정된 행의 개수가 0이면 이미 누가 데이터 수정한 것 -> 트랜잭션 종료 시점에 익셉션 발생 -> OptimisticLockingFailureException 발생
    • 사용자가 폼을 서버에 전송할 때 함께 전송한 버전과 애그리거트 버전이 동일한 경우에만 애그리거트 수정 기능 수행하도록 함 -> 트랜잭션 충동 문제 해소

  • 비선점 잠금 방식을 여러 트랜잭션으로 확장하려면 애그리거트 정보를 뷰로 보여줄 때 버전 정보도 함께 사용자 화면에 전달
    • HTML 폼 생성하는 경우 버전 값을 갖는 hidden 타입 <input> 태그 생성해서 버전 값 서버에 함께 전달
    • 응용 서비스에 전달할 요청 데이터는 사용자가 전송한 버전 값 포함
    • 표현 걔층은 버전 충돌 익셉션이 발생하면 버전 충돌을 사용자에게 알려 사용자가 알맞은 후속 처리를 할 수 있도록 함
    • 버전 충돌 상황에 대한 구분이 명시적으로 필요 없다면 응용 서비스에서 프레임워크용 익셉션 발생시키는 것도 고려 가능

8.3.1 강제 버전 증가

  • 애그리거트 루트 외에 다른 엔티티가 존재하는 애그리거트가 기능 실행 도중 루트가 아닌 다른 엔티티의 값만 변경된 경우 -> JPA는 루트 엔티티의 버전 값 증가 시키지 않음 -> 강제 버전 증가 필요
    • EntityManager#find() 메서드로 엔티티를 구할 때 강제로 버전 값을 증가시키는 잠금 모드 지원
    • LockModeType.OPTIMISTIC_FORCE_INCREMENT를 사용하면 해당 엔티티의 상태 변경에 상관없이 트랜잭션 종료 시점에 버전 값 증가 처리

8.4 오프라인 선점 잠금


  • 오프라인 선점 잠금 방식(Offline Pessimistic Lock)
    • 여러 트랜잭션에 걸쳐 동시 변경을 막음
    • 첫 번째 트랜잭션 시작할 때 오프라인 잠금을 선점하고, 마지막 트랜잭션에서 잠금 해제
    • 잠금 해제 전까지 다른 사용자는 잠금 구할 수 없음
    • 잠금을 해제하지 않고 프로그램 종료될 수 있으므로 유효 시간 지나면 자동으로 잠금해제하도록 잠금 유효 시간 가져야 함
    • 일정 주기로 유효 시간 증가키시는 방식 필요

8.4.1 오프라인 선점 잠금을 위한 LockManager 인터페이스와 관련 클래스

  • 오프라인 선점 잠금은 크게 잠금 선점 시도, 잠금 확인, 잠금 해제, 잠금 유효시간 연장의 4가지 기능 필요
package com.myshop.lock;

public interface LockManager {
	LockId tryLock(String type, String id) throws LockException; // 두 파라미터는 각각 잠글 대상 타입과 식별자
    
    void checkLock(LockId lockId) throws LockException;
    
    void releaseLock(LockId lockId) throws LockException;
    
    void extendLockExpiration(LockId lockId, long inc) throws LockException;
}

8.4.2 DB를 이용한 LockManager 구현

예시 코드는 책 참고하기

0개의 댓글

관련 채용 정보