- 개념적으로 동일한 애그리거트지만 물리적으로 서로 다른 래그리거트 객체를 사용한다. 서로의 애그리거트 객체에 영향을 주지 않는다.
두 스레드는 각각 트랜잭션을 커밋할 때 수정한 내용을 DB에 반영한다. 이 순서의 문제점은 운영자는 기존 배송지 정보를 이용해서 배송 상태로 변경했지만, 고객은 배송지 정보를 변경했다는 점이다.
-> 래그리거트의 일관성이 깨진다.
- 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하지 못하게 막는다.
- 상대방 스레드가 먼저 선점한 잠금을 구할 수 없어 교착 상태에 빠지게 되는 경우가 있다.
- 사용자 수가 많을 때 발생할 가능성이 높고 사용자 수가 많아지면 교착 상태에 빠지는 스레드가 더 빠르게 증가한다.
hints
를 사용한다.hints.put("javax.persistence.lock.timeout", 2000)
@QueryHints
애너테이션을 사용해서 쿼리 힌트를 지정할 수 있다.@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHint({
@QueryHint(name = "javax.persistence.lock.timeout", value = "2000")
})
- 선점 잠금으로 모든 트랜잭션 충돌 문제가 해결되는 것은 아니다.
- 동시에 접근하는 것을 막는 대신 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식
애그리거트에 버전으로 사용할 숫자 타입 프로퍼티를 추가한다음 애그리거트를 수정할 때마다 버전으로 사용할 프로퍼티 값이 1씩 증가한다.
수정할 애그리거트와 매핑되는 테이블의 버전 값이 현재 애그리거트의 버전과 동일한 경우에만 데이터를 수정한다. 다른 트랜잭션이 먼저 데이터를 수정해서 버전 값이 바뀌면 데이터 수정에 실패하게 된다.
- 동시에 수정하는 것을 막고 싶을 때 사용한다.
오프라인 선점 잠금은 크게 잠금 선점 시도, 잠금 확인, 잠금 해제, 잠금 유효시간 연장의 네 가지 기능이 필요하다.
interface LockManager {
fun tryLock(type: String, id: String): LockId
fun heckLock(lockId: LockId)
fun releaseLock(lockId: LockId)
fun extendLockExpiration(lodckId: LockId, inc: Long)
}
잠금을 구하면 잠금을 해제하거나 잠금이 유효한지 검사하거나 잠금 유효 시간을 늘릴 때 LockId
를 사용한다.
class Lock(private var _value: String) {
fun LockId(value: String) {
this._value = value
}
val value: String
get() = _value.ifEmpty { "" }
}
오프라인 선점 잠금이 필요한 코드는 LockManager#tryLock()
을 이용해서 잠금을 시도한다.