From 413a05218ab64b9d10a5fc379819130052e53107 Mon Sep 17 00:00:00 2001 From: dlswns2480 Date: Tue, 30 Jul 2024 17:16:27 +0900 Subject: [PATCH] =?UTF-8?q?docs=20:=208=EC=9E=A5=20=EC=95=A0=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EA=B1=B0=ED=8A=B8=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\354\265\234\354\235\270\354\244\200.md" | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 "chap08/\354\265\234\354\235\270\354\244\200.md" diff --git "a/chap08/\354\265\234\354\235\270\354\244\200.md" "b/chap08/\354\265\234\354\235\270\354\244\200.md" new file mode 100644 index 0000000..a33b0ef --- /dev/null +++ "b/chap08/\354\265\234\354\235\270\354\244\200.md" @@ -0,0 +1,111 @@ +## 애그리거트와 트랜잭션 + + + +위 그림과 같은 상황이 있다고 해보자. + +운영자 스레드와 고객 스레드는 개념적으로 동일한 애그리거트(주문)지만 **물리적으로** 서로 다른 애그리거트 객체를 사용한다. + +위 그림의 문제는 운영자는 배송중 상태로 변경했는데 고객은 그 사이 배송지 정보를 변경했다. 결국 고객은 원하는 상품을 이전에 설정해놓았던 배송지로 배송받게 된다. + +이는 일관성이 깨지므로 문제이고 다음 두 가지 중 하나로 해결해야 한다. + +- 운영자가 배송지 정보를 조회하고 상태를 변경하는 동안, 고객이 애그리거트를 수정하지 못하게 막는다. +- 운영자가 배송지 정보를 조회한 이후에 고객이 정보를 변경하면, 운영자가 애그리거트를 다시 조회한 뒤 수정하도록 한다. + +이 두가지는 애그리거트를 위한 추가적인 트랜잭션 처리 기법이 필요하다. + +트랜잭션 처리 방식에는 **선점 잠금과 비선점 잠금이 있다.** + +## 선점 잠금 + +선점 잠금은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하지 못하게 막는 방식이다. 다음 그림 예시와 같다. + + + + +스레드1이 애그리거트를 수정하고 트랜잭션을 커밋하면 잠금을 해제한다. + +이후에는 스레드2가 애그리거트에 접근하게 된다. + +선점 잠금은 보통 DBMS가 제공하는 행단위 잠금을 사용해서 구현한다. + +오라클을 비롯한 다수의 DBMS가 for update와 같은 쿼리를 사용해서 특정 레코드에 한 커넥션만 접근할 수 있는 잠금장치를 제공한다. + +스프링 데이터 JPA에서는 @Lock 어노테이션을 사용해서 잠금 모드를 지정한다. + +```java +public interface MemberRepository extends Repository { + @Lock(LockmodeType.PESSIMISTIC_WRITE) + @Query("select m from Member m where m.id = :id") + Optional findByIdForUpdate(@Param("id") Long id); +} +``` + +### 선점 잠금과 교착 상태 + +선점 잠금 기능을 사용할 때는 데드락이 발생하지 않도록 특히 주의해야 한다. + +다음과 같은 상황이 있다고 해보자 + +1. 스레드1: A 애그리거트에 대한 선점 잠금 구함 +2. 스레드2: B 애그리거트에 대한 선점 잠금 구함 +3. 스레드1 : B 애그리거트에 대한 선점 잠금 시도 +4. 스레드2: A 애그리거트에 대한 선점 잠금 시도 + +이 순서에 따르면 서로 상대방 스레드가 먼저 선점한 잠금을 구할 수 없어 데드락에 빠진다. + +이는 사용자 수가 많을 때 발생할 가능성이 높고, 사용자 수가 많아지면 데드락에 걸리는 스레드는 더 빠르게 증가한다. + +→ 이런 문제가 발생하지 않도록 하려면 잠금을 구할 때 최대 대기 시간을 지정해야 한다. + +→ 최대 대기 시간이 지나도 잠금을 못 구하면 익셉션을 발생시킨다. + +## 비선점 잠금 + +선점 잠금이 강력해 보이긴 하지만 선점 잠금으로 모든 트랜잭션 충돌 문제가 해결되는 것은 아니다. + +다음 그림 예시를 보자. + + + + +위 예시에서 문제는 운영자가 배송지 정보를 조회하고 배송 상태로 변경하는 사이에 고객이 배송지를 변경한다는 것이다. + +이 문제는 선점 잠금으로는 해결할 수 없다. 비선점 잠금으로 해결해야 한다. + +비선점 잠금은 동시에 접근하는 것을 막는 대신 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식이다. + +비선점 잠금은 애그리거트의 버전 프로퍼티를 활용하여 수정할 애그리거트의 버전 값이 현재 애그리거트의 버전과 동일한 경우에만 데이터를 수정한다. 그림 예시는 다음과 같다. + + + +비선점 잠금을 활용할 때 응용 서비스는 버전에 대해 알 필요가 없다. + +리포지터리에서 필요한 애그리거트를 구하고 알맞은 기능만 실행하면 된다. + +비선점 잠금을 위한 쿼리를 실행할 때 쿼리 실행결과로 수정된 행의 개수가 0이면 이미 누군가 앞서 데이터를 수정한 것이고 이는 트랜잭션이 충돌한 것이므로 익셉션이 발생한다. + +### 강제 버전 증가 + +애그리거트 루트 외에 다른 엔티티가 존재하는데 기능 실행 도중 루트가 아닌 다른 엔티티의 값만 변경된다고 해보자. 이 경우엔 버전을 어떻게 해야할까? 결론은 루트 애그리거트의 버전 값도 증가해야 한다. + +루트 엔티티의 값이 바뀐게 아니어도 애그리거트 구성요소 중 일부 값이 바뀐거면 논리적으로 그 애그리거트는 바뀐 것이다. + +→ JPA는 이를 처리하기 위해 엔티티를 구할 때 강제로 버전 값을 증가시키는 잠금 모드를 지원한다. + +## 오프라인 선점 잠금 + +여러 사람이 동시에 문서 편집화면을 볼 수 있을 때 한 사람이 수정하고 있다면 다른 사람은 수정하는 것을 엄격하게 막기 위해선 **오프라인 선점 잠금**이 필요하다. + +단일 트랜잭션에서 동시 변경을 막는 선점 잠금 방식과 달리 오프라인 선점 잠금은 여러 트랜잭션에 걸쳐 동시 변경을 막는다. + +수정기능을 예로 생각해보자. 수정 기능은 보통 두 개의 트랜잭션으로 구성되고 예시는 다음과 같다. + + + +만약 A가 과정 3의 수정 요청을 수행하지 않고 프로그램을 종료한다면? + +다른 사용자는 영원히 잠금을 구할 수 없는 상황이 발생한다. + +그렇기에 오프라인 선점 잠금 방식은 잠금 유효 시간을 가져야 하고 자동으로 잠금이 해제되도록 해야 한다. \ No newline at end of file