DDD 도메인주도설계

도메인주도개발 시작하기 8장 애그리거트 트랜잭션관리

MIN우 2024. 2. 22. 16:23
728x90

8.1 애그리거트와 트랜잭션

한 주문 애그리거트에 대해 운영자는 배송 준비 상태로 변경할 때 사용자는 배송지 주소를 변경한다면 어떻게 될까

  • 스레드는 각각 트랜잭션을 커밋할 때 수정한 내용을 DBMS에 반영함.
  • 때문에 하나의 애그리거트를 여러 사용자가 동시에 변경하고자한다면 데이터의 일관성이 깨질 수 있음

이런 문제를 방지하기 위해서는 아래와 같은 방법을 사용해야 함

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

낙관적락 : 충돌이 발생하지 않을거야 라고 낙관적으로 생각함 (비선점) -> 버전관리

비관적락 : 충돌이 무조건 발생할것이라고 비관적으로 생각함 (선점) -> 데이터베이스 synchronized 격리수준

 

위와 같이 일관성이 깨지지도 않도록 하는 방법은 트랜잭션 처리와 관련이 있음
애그리거트에 대해 사용할 수 있는 대표적인 트랜잭션 처리 방식에는 의 두 가지 방식이 존재함

  • 선점(Pessimistic) 잠금
  • 비선점(Optimistic) 잠금

격리수준 4단계

  1. READ UNCOMMITTED (미순읽기):
    • 다른 트랜잭션에서 수정 중인 데이터를 읽을 수 있음.
    • Dirty Read 발생 가능 (커밋되지 않은 다른 트랜잭션의 변경사항을 읽을 수 있음).
  2. READ COMMITTED (순읽기):
    • 커밋된 데이터만 읽을 수 있음.
    • Non-Repeatable Read 발생 가능 (동일한 쿼리를 실행했을 때, 다른 트랜잭션이 커밋한 데이터가 다르게 조회될 수 있음).
  3. REPEATABLE READ (반복가능한 읽기):
    • 트랜잭션 내에서 같은 쿼리를 여러 번 실행해도 항상 동일한 결과를 보장함.
    • Phantom Read 발생 가능 (동일한 쿼리를 실행했을 때, 다른 트랜잭션이 새로운 데이터를 추가하거나 삭제함으로써 조회되는 데이터가 달라질 수 있음).
  4. SERIALIZABLE (직렬화 가능):
    • 가장 높은 격리 수준.
    • Phantom Read, Dirty Read, Non-Repeatable Read가 발생하지 않음.
    • 트랜잭션 간에 완전한 격리를 유지하기 위해 트랜잭션을 직렬화함 (즉, 동시에 여러 트랜잭션을 실행할 때, 순차적으로 실행됨).

더티 리드 : 동시에 진행되고 있는 다른 트랜잭션(아직 커밋하지 않은 상태)에서 변경한 데이터를 현재 진행 중인 트랜잭션에서 읽어 들이는 것을 뜻합니다.

팬텀 리드 (Phantom read) : 한 트랜잭션 내에서 같은 쿼리문이 실행되었음에도 불구하고 조회 결과가 다른 경우를 뜻한다

 

대채로 어플리케이션 대부분은 동시성 처리가 중요해서 데이터베이스들은 보통 Read committed 수준을 기본으로 사용한다.

8.2 선점 잠금

선점 잠금은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식


한 스레드가 애그리거트를 구하고 수정하는 동안 다른 스레드가 수정할 수 없도록 하여 일관성을 지킬 수 있음

선점 잠금은 보통 DBMS 가 제공하는 행 단위 잠금을 사용해서 구현

  • 일반적으로 DBMS 에서는 for update 와 같은 쿼리를 사용해서
    특정 레코드에 한 사용자만 접근할 수 있는 잠금 장치를 제공함
  • JPA 의 EntityManager 에서는 LockModeType.PESSIMISTIC_WRITE 사용
  • 스프링 데이터 JPA에서는 @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션 사용

선점 잠금은 보통 DMBS가 제공하는 행단위 잠금을 사용해서 구현한다.

Jpa EntityManager.find() 함수를 통해 선점 잠금 방식을 사용할 수 있다.  LockModeType을 PESSIMISTIC_WRITE를 사용하면 된다.

Data-jpa 에서는 @Lock Annocation을 지원한다.

선점 잠금 기능 사용시에는 교착 상태(Deadlock)가 발생하지 않도록 주의해야 한다.

데드락에 빠지지 않게 하려면 잠금을 구할 때 최대 대기 시간을 지정해야 한다.

data-jpa애서는 @QueryHInts 에너테이션을 사용해서 쿼리 힌트를 지정할 수 있다.

 

SELECT ~ FOR UPDATE 는 선택된 행들에 대해 배타적으로 LOCK을 거는 것이다.

즉 "데이터 수정하려고 SELECT 했어. 건드리지마 !" 와 같은 뜻이다. 

여기서 그럼 배타락과 공유락의 차이는 ?

 데이터베이스의 Lock은 크게 두가지가 있다.
      1. Shared Lock (공유락) - 읽기 O , 쓰기 X 

         -  다른 트랜잭션에서의 Shared Lock은 허용하지만 Exclusive Lock은 허용하지 않음 

      2. Exclusive Lock (배타락) - 읽기 X, 쓰기 X

         -  다른 트랙잭션에서의 Shared Lock, Exclusvie Lock 은 허용하지 않는다.

 

공유락

배타락

비선점 , 선점 , 분산락 정리

 

분산락 redisson을 사용해서 보면 좋을 것 같다.

but 무조건 낙관적락보다 redisson이 좋냐 ...? 서버의 규모가 크고 스케일 아웃된 환경,스케일 아웃된 DB, 충돌이

많은 환경에서는 좋을 것 같다 하지만 유지보수하는 비용이 많이든다 , 그러므로  년에 1~2번정도로 충돌이 

잦지 않고 단일DB를 사용하고있다면 낙관적락을 사용해서 특정 레코드에 한 사용자만 접근할 수 있는 잠금 장치를 하고

정말 데이터의 동시성이나 동기화가 중요한 작업은 비관적락을 사용하여 dbms에서 제공하는 synchronized을 걸어

성능은 조금포기하더라도 데이터의 정합성과 일관성에 중점을 둬야할 것 같다. 

 

 

 

https://minwoo-it-factory.tistory.com/entry/Lock-%EC%A0%95%EB%A6%AC%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD%EA%B3%BC-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B6%84%EC%82%B0%EB%9D%BD-%EB%8D%B0%EB%93%9C%EB%9D%BD-%EB%B0%8F-%ED%99%9C%EC%9A%A9%EA%B9%8C%EC%A7%80

 

Lock 정리(낙관적 락과 비관적 락, 분산락, 데드락) 및 활용까지

낙관적 락(optimistic Lock) - 충돌이 발생하지 않는다고 낙관적이라고 가정함. - DB가 제공하는 락 기능이 아니라 어플리케이션에서 제공하는 버전관리 기능을 사용함. - version 등의 구분칼럼을 사용

minwoo-it-factory.tistory.com

 

 

참고 : https://055055.tistory.com/111

728x90