개요
한 주문 애그리거트에 대해 운영자는 배송상태로 변경할 때, 사용자는 배송지 주소를 변경하면 어떻게 될까?
⇒ 8장에서는 동일한 데이터에 대해 두 가지 방식의 요청이 들어올 때, 일관성을 깨지 않고 데이터를 변경하는 방법에 대해 다룬다.
⇒ 추가적인 트랜잭션 처리 기법을 다룬다.
⇒ 위의 예시에 대해서는 다음과 같은 방법이 있는데,
- [선점] 운영자가 배송지 정보를 조회하고 상태를 변경하는 동안 / 고객은 애그리거트 수정 불가
- [비선점] 운영자가 배송지 정보를 조회한 이후 / 고객이 정보 변경 / 운영자가 애그리거트 다시 조회 뒤 수정
선점 잠금
선점 잠금(Pessimistic Lock): 먼저 애그리거트를 구한 스레드가 애그리거트 사용에 대한 우선권을 가짐
→ 한 스레드의 애그리거트 사용이 끝날때까지, 다른 스레드는 애그리거트를 사용할 수 없음
→ code 어떻게 구현하는가?
- 스레드 1이 애그리거트를 구함(코드로 불러옴) + 트랜잭션 커밋 잠금
- 커밋이 잠금된 동안 접근이 금지(Blocking)됨
→ DBMS 어떻게 구현하는가?
- 행단위 잠금
- 잠금(LOCK) 종류와 잠금수준
- 오라클 등 다수 DBMS(mysql 포함)는 쿼리를 통해 특정 레코드에 한 커넥션만 접근할 수 있는 잠금잠치 제공
- 더 알아보면 좋은 것: Row Lock, Shared Lock, Exclusive Lock
구현
스프링 하이버네이트 - LockModeType 설정
private final EntityManager entityManager;
Order order = entityManager.find(
Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE
);
스프링 데이터 JPA - 어노테이션 사용
@Lock(LockModeType.PESSIMISTIC_WRITE)
// @Query("")
Optional<Order> findByIdForUpdate(Long orderNo);
주의사항
- 데드락 발생 주의
- 데드락: 교착 상태. 일련의 프로세스들이 서로가 가진 자원을 기다리며 블락된 상태1) Mutual exclusion: 매 순간 하나의 프로세스만이 자원을 사용할 수 있는데, 동시에 여러 프로세스가 자원에 접근 할 때 발생3) Hold & Wait: 자원을 가진 프로세스가 / 다른 자원을 기다릴 때 / 보유 자원을 놓지 않고 계속 갖고 있어서 발생➡️ 자원 할당 그래프로 사이클 확인: 박스는 리소스, 큰 원은 프로세스, 리소스 안의 점은 리소스의 개수를 의미한다</aside>
- 그래프에 사이클이 없으면 데드락이 아니다. 인스턴스 한 개 할당은 무조건 데드락, 이외에는 가능성만 존재
- 4) Circular wait: 자원을 기다리는 프로세스 간에 사이클 형성
- 2) No preemption: 프로세스는 자원을 스스로 내어놓을 뿐 강제로 빼앗기지 않기 때문에 발생. 내어놓을 때까지 기다려야 함
- <aside> 💡 데드락 발생 조건
- RDBS의 경우 → 애그리거트에 대한 잠금을 선점할 경우, 영원히 잠금 선점이 불가능한 사태 발생
⇒ 해결 방법: 잠금을 구할 최대 대기 시간 지정
스프링 하이버네이트 - LockModeType 설정
private final EntityManager entityManager;
hint.put("javax.persistence.lock.timeout", 2000);
Order order = entityManaber.find(
Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE
);
스프링 데이터 JPA - 어노테이션 사용
@Lock(LockModeType.PESSIMISTIC_WRITE)
// @Query("")
@QueryHints({name = "javax.persistence.lock.timeout", value = "2000"})
Optional<Order> findByIdForUpdate(Long orderNo);
비선점잠금
- 선점 잠금이 매우 강력하지만, 모든 트랜잭션 충돌 문제가 해결되지는 않음
- 비선점잠금: 동시 접근을 막는 대신 변경 데이터를 DBMS에 반영하는 시점에 변경 가능 여부를 확인
- 애그리거트(Table)에 ‘버전’ 컬럼 추가
구현
엔티티의 필드에 Version 어노테이션 추가
public class Order {
@Version
private Long version;
}
- 업데이트문 발생 시, version이 동일해야 변경 가능
- 자연스럽게 트랜잭션 충돌이 발생하여 OptimisticLockingFailureException 발생
주의 사항: 강제 버전 증가
- 루트 엔티티 이외의 다른 엔티티의 값만 변경될 때, 루트의 버전 값은 증가시키지 않음.
- 위와 같은 상황 발생하면 안 됨(애그리거트 관점에서). 구성요소 중 하나가 바뀌면 전체 증가해야 함.
스프링 하이버네이트 - LockModeType 설정
private final EntityManager entityManager;
hint.put("javax.persistence.lock.timeout", 2000);
Order order = entityManaber.find(
Order.class, orderNo, LockModeType.OPTIMISTIC_FORCE_INCREMENT
);
스프링 데이터 JPA - 어노테이션 사용
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
// @Query("")
Optional<Order> findByIdForUpdate(Long orderNo);
오프라인 선점 잠금
여러 트랜잭션에 걸쳐 동시 변경을 막음.
- 첫 트랜잭션에서 오프라인 잠금 선점, 마지막 트랜잭션에서 잠금 해제
- 잠금 해제 전까지 다른 사용자는 절대 잠금 선점 불가
구현
스프링 내부에서 구현할 경우
따로 LockManager 인터페이스를 구현해야 함
RDBMS로 구현할 경우
- lock의 정보를 저장하기 위한 테이블을 생성
- 테이블에 알맞는 엔티티 생성
어떻게 활용할 수 있을까?
한 화면에서 여러 사용자가 동시 접속할 때 → 필수적으로 구현해야 함
스프링 MVC 패턴에서, 서버와 클라이언트를 동시 배포하면서 + 다중 뷰 수정이 필요할 때
일반적인 프로젝트에서 활용할 내용은 아님.
노션이나 구글독스, 피그마같은 프로그램에서는 필수적으로 구현해야 할 것
예제를 보니 전체가 돌아가는 코드가 필요한데, 이외의 경우에는 오류가 쉽게 나기 때문에, 후에 토이프로젝트로 두세명이서 구현해보는 걸 추천합니다(해보고싶다,,,)
'Programming > Spring' 카테고리의 다른 글
[Spring] JwtException에서만 Cors에러가 발생할 때 (0) | 2023.10.29 |
---|