Lock
1 Silent Data Loss
- 먼저 Lock에 대해 알아보기 전에 Lock으로 해결할 수 있는 Silent Data Loss 문제에 대해 알아보자
- Second Lost Update Problem라고 불리기도 한다.
- Silent Data Loss는 데이터베이스 트랙잭션 범위를 넘어서는 문제이다.
- Optimistic Locking과 Pessimistic Locking을 통해 Silent Data Loss 문제를 해결할 수 있다.
- 두 가지의 케이스를 보면서 Silent Data Loss 문제가 무엇이고 어떻게 발생하는지 알아보자.
1.1 Case1: Concurrent Database Transactions Problem

- Transaction 1과 Transaction 2가 같은 상태의 아이템을 읽는다
- 두 트랜잭션이 한 아이템에 대해서 다른 수정작업을 한다
- Transaction 1은 아아템의 amount를 5 증가시킨다
- Transaction 2은 아아템의 amount를 10 증가시킨다
- Transaction 2가 먼저 커밋하고 수정 사항을 데이터베이스에 영속화한다
- 잠시 뒤 Transaction 1이 커밋하면서 Transaction 2의 수정사항을 덮어쓴다
- 아이템의 amount 15가 되는 것을 기대했지만 최종적으로 Transaction 2의 수정사항이 없어져 amount가 5가 되었다
- 이러한 문제를 Silent Data Loss 또는 Second Lost Update Problem이라고 한다
1.2 Case2: Concurrent Long Conversations Problem

- 두 명의 사용자가 똑같은 아이템을 수정하고 있다([SAVE] 버튼이 있는 GUI 폼을 통해서)
- 두 명의 유저 모두 같은 상태의 아이템을 가지고 있다
- 유저 1은 Transaction1을 통해 아이템을 가져온다
- 유저 2은 Transaction2을 통해 아이템을 가져온다
- Transaction1과 Transaction2가 종료된 상태에서 수정 작업을 진행한다
- 즉 수정 작업은 트랜잭션 밖에서 진행된다
- 트랜잭션을 계속 유지한다면 성능상 좋지 않기 때문
- 유저2가 SAVE 버튼을 눌러 수정을 완료하면 유저2의 수정 사항이 Transaction3을 통해 데이터베이스의 영속화된다
- 잠시 뒤 유저1이 SAVE 버튼을 눌러 수정을 완료하면 Transaction4를 통해 유저 2의 수정 사항을 덮어쓴다
- 아이템의 amount 15가 되는 것을 기대했지만 최종적으로 유저 2의 수정사항이 없어져 amount가 5가 되었다
- 이러한 문제를 Silent Data Loss 또는 Second Lost Update Problem이라고 한다
1.3 Silent Data Loss 해결책
- 해결책으로 3가지 선택 방법이 있다
- 마지막 커밋민 인정
- 최초 커밋만 인정
- 충돌하는 갱신 내용 병합
- JPA가 제공하는 버전 관리 기능을 사용하면 손쉽게 최초 커밋만 인정하기를 구현할 수 있다
2 Optimistic Locking
- Optimistic Locking은 기본적으로 각 트랜잭션이 같은 레코드를 변경할 가능성이 희박할 것이라고 가정한다
- 따라서 우선 변경 작업을 수행하고 마지막에 잠금 충돌이 있었는지 확인해 문제가 있다면 ROLLBACK 처리한다
- 데이터베이스가 제공하는 Lock 기능을 사용하는 것이 아니라 JPA가 제공하는 버전 관리 기능을 사용한다
- 쉽게 말하면 애플리케이션이 제공하는 락
- 낙관적 락은 트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌을 알 수 없다
- JPA의 @Version을 사용하면 트랜잭션이 출동하면서 발생하는 Silent Data Loss 문제를 해결할 수 있다
- 엔티티에 버전 필드를 두어 이 문제를 해결하는데 자세한 과정은 아래를 참고하자
2.1 Case1: Concurrent Database Transactions Solution

- Silent Data Loss 문제를 위해 Item 엔티티에 version이라는 필드를 추가했다. 이 필드는 충돌이 있는지 확인하기 위한 용도이다
- Transaction1이 Item을 읽었다 이 때 Item의 version은 0이다
- Transaction2이 Item을 읽었다 이 때 Item의 version은 0이다
- Transaction2이 Item의 amount를 10 증가시키고 version을 1 증가시키는 UPDATE 쿼리를 보낸다. (WHERE절에 version=0이라는 조건을 추가)
- Transaction1이 Item의 amount를 5 증가시키고 version을 1 증가시키는 UPDATE 쿼리를 보내지만 예외가 발생한다 (WHERE절에 version=0이라는 조건을 추가)
- Item의 현재 버전이 version이 1로 증가 했기 때문에 이 조건을 만족하는 Item을 찾을 수 없다 이 때 JPA는 버전이 이미 증가한 것으로 판단해서 예외를 발생시킨다.
- Transaction1은 예외 발생 이후 다시 Item(version1)을 읽어와 amount를 5 증가시키고 version을 1 증가시키는 UPDATE 쿼리를 보낸다. 최종적으로 count가 15가 되었다
2.2 Case2: Concurrent Long Conversations Solution

- 같은 방식으로 Silent Data Loss 문제를 해결할 수 있다.
3 @Version
- JPA가 제공하는 Optimistic Locking을 사용하려면 엔티티에 @Version 애노테이션이 붙은 필드가 필수적으로 필요합니다.
- 이 필드를 버전 애트리뷰트라고 합니다.
- 버전 애트리뷰트가 있는 엔티티를 수정할 때 마다 버 전이 하나씩 자동으로 증가합니다.
- 그리고 엔티티를 수정할 때 조회 시점의 버전과 수정 시점의 버전이 다르면 예외가 발생합니다. (OptimisticLockException이 발생)
- 예: 트랙잭션1이 조회한 엔티티를 수정하고 있는데 트랙잭션2에서 같은 엔티티를 수정하고 커밋해서 엔티티의 버전이 증가한다. 이후 트랜잭션1이 커밋할 때 버전 정보가 다르므로 예외 발생
3.1 사용 예시
@Entity
public class Item {
@Id
private Long id;
private Long amount;
@Version
private Long version;
}