https://www.youtube.com/watch?v=LDi5muN2kgI
목차
- 1. Lock은 무엇일까?
- 2. Lock과 Transaction ?
- 3. Lock과 전략
- 4. JPA에서의 낙관적 & 비관적 락
1. Lock은 무엇일까?
- 데이터가 존재한다. 이떄 여러 COnnection A, B, C, D 4개가 데이터 수정을 원할경우 데이터에 문제가 발생한다.
- Connection이 오는 순서에 따라서 데이터 값이 어떻게 될지 모르기에 데이터 일관성에 오류가 생긴다.
- 이떄 데이터의 일관성 해결방안 중 하나가 Lock이다.
- Connection A가 데이터에 접근할떄 자물쇠를 잠구어서 다른곳에서 접근할 수 없다.
- 이떄 이렇게 데이터에 접근할 수 없게 자물쇠를 잠근다는 개념을 Lock이라고한다.
2. Lock과 Transaction ?
- Lock을 알아갈수록 Lock과 Transaction이 헷갈린다고 한다.
- 차이점을 알아보자.
- Lock은 동시성 제어
- 트랜잭션 (All or Nothing, 작업의 원자성)
- 왜 헷갈렸을까? 트랜잭션 격리 수준때문에 헷갈린다.
- Lock
- 동시에 발생하는 수정요청에 대한 데이터 일관성을 지키기 위한 메커니즘이다.
- 트래잭션
- 여러 트랜잭션에 대해 각 트랜잭션들을 어떻게 처리할지에 대한 전략
- Lock
- 즉, 이 각 트랜잭션들을 어떻게 처리할지에 대한 전략 중 구현방법 중 하나가 Lock이다.
- 격리 수준을 구현하는 방법 중하나가 Lock인것이다.
- Lock이 트랜잭션 격리 수준에 포함된다고 생각한다.
3. Lock과 전략
- 낙관적 Lock
- Transaction이 애초에 충돌이 발생하지 않는다고 생각하고 진행한다.
- 애플리케이션 Lock이라고도 한다. 애플리케이션 내부에서 Version이라는 것을 통해서 해당 내부에서 구현할 수 있기에 애플리케이션 Lock이라고도 한다.
- 비관적 Lock
- 애초에 Transaction이 매번 충돌이 발생한다고 가정하고 사용한다.
- 데이터베이스 트랜잭션 Lock 이라고한다.
- SELECT FOR UPDATE 가 있다. 데이터베이스 update를 할떄 트랜잭션이 발생할 수 있기에 바로 Lock을 건다.
4. JPA에서의 낙관적 & 비관적 락
- 테스트 상황 제시: 쿠폰이 5개가 존재한다. 총 20명의 사용자가 동시에 5개의 쿠폰을 발급하는 테스트를 해보자.
- NormalCoupon.class
@Entity
@NoArgsConstructor
@Getter
public class NormalCoupon{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int count;
public NormalCoupon(final int count){
this.count = count;
}
public void issue(){
if(count <= 0){
throw new IllegalArgumentException("수량 부족");
}
count -= 1;
}
}
- 테스트코드..class
- 일반적인 상황에서의 테스트이다.
- Java에서의 동시성테스트는 ExecutorService 와 CountDownLatch 가 있다.
@Test
@DisplayName("동시에 쿠폰을 발급하게 되면 존재하 쿠폰의 개수 이상으로 쿠폰이 발급될 수 있다.")
void test_not_lock() throws InterruptedException{
final int executeNumber = 20;
final ExecutorService executorService = Executors.nexFixedThreadPool(10);
final CountDownLatch countDownLatch = new CountDounLatch(executeNumber);
final AtomicInteger successCount = new AtomicInteger();
final AtomicInteger failCount = new AtomicInteger();
for(int i=0;i<executeNumber; i++){
executorService.execute( () -> {
try{
normalCouponService.issueCoupon(1L);
successCount.getAndIncrement();
System.out.println("쿠폰 발급");
} catch(Exception e){
failCount.getAndIncrement();
System.out.println(e.getMessage());
}
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("발급된 쿠폰의 개수 = "+ successCount.get());
System.out.println("실패한 횟수 = "+ failCount.get());
assertEquals(failCount.get() + successCount.get(), executeNumber);
}
결과값
- 발급된 쿠폰의 개수는 20개다. 우리는 5개의 쿠폰만 존재하는데 20개가 발급되었다.
발급된 쿠폰의 개수 = 20
실패한 횟수 = 0
- 낙관적 락을 활용하여 해결해보자.
- Version 은 어렵게 생각하지않고, 버전 1, 2 3 4 라고 생각한다.
- Entity의 접근해서 값이 변경될때 이 버전이 같이 증가한다.
- 현재 버전이 맞는지, 아닌지 검사/알기 위한 숫자다.
- @Version을 붙여준 이유는 이 어노테이션을 통해 해당 변수 version에 +1 해주기 위한 어노테이션이다.
@Entity
@NoArgsConstructor
@Getter
public class NormalCoupon{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int count;
@Version
private Integer version;
public NormalCoupon(final int count){
this.count = count;
}
public void issue(){
if(count <= 0){
throw new IllegalArgumentException("수량 부족");
}
count -= 1;
}
}
- TEST를 진행해보면
- 아래의 것이 나온다.
- 현재 버전이 맞는지 확인한뒤 값을 변경하고 version도 변경하는 모습을 보인다.
update optimistic_coupon
set count=3, version=2
where id=1 and version=1;
- 20명의 사용자가 5장의 카드를 발급했는데 왜 3개만 발급되었을까?
- JPA에서 낙관적 락은 최초의 요청만 Commit하기 때문이다.
성공한 횟수 = 3
낙관적 락 획득 실패횟수 = 17
갯수 부족 횟수 = 0
- 아까와 같이 count가 존재한다.
- Transaction A가 count값을 수정하기 위해 Transaction이 시작한다. 이떄 Version = 1 이다.
- 동시에 TransactionB도 count값을 수정하기 위해 Transaction이 시작한다. 이떄 Version = 1이다.
- 동시에 TransactionC도 count값을 수정하기 위해 Transaction이 시작한다. 이떄 Version = 1이다.
- 동시에 TransactionD도 count값을 수정하기 위해 Transaction이 시작한다. 이떄 Version = 1이다.
- TransactionA update ... version =2 where version = 1
- TransactionB update ... version =2 where version = 1 (버전이 맞지 않아 안됨)
- TransactionC update ... version =2 where version = 1 (버전이 맞지 않아 안됨)
- TransactionD update ... version =2 where version = 1 (버전이 맞지 않아 안됨)
- 그렇기에 JPA에서 낙관적락은 최초의 요청이 발생하기 때문에 동시성테스트는 JVM이 어떻게 작동하느냐에 따라 쿠폰의 개수가 달라질 수 있다.
- 가장 중요한점은 보통상황에서와는 다르게 그래도 쿠폰의 개수가 5개는 넘지않는것은 보장된다.
JPA에서의 비관적락
public interface PessimisticCouponRepository extends JpaRepository<PermisticCoupon, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<PessimisticCoupon> findById(Long id);
}
- JPA에서의 비관적락은 @Lock 어노테이션과 함께 PESSIMISTIC_WRITE를 사용한다.
- 테스트 진행해보면 정확하게 5개의 쿠폰이 사용된다.
- 왜 비관적락은 정확히 5개 쿠폰이 발급되고, 낙관적 락은 실패했을까?
- 비관적 락은 데이터에 접근할때부터 바로 자물쇠를 잠그기 때문에 20명의 사용자가 동시에 쿠폰을 발급할 경우에는 트랜잭션 1개가 들어가는 순간 나머지 1개는 대기하게 된다.
- 5개가 채운뒤 부족하면 바로 Exception이 발생한다.
사용된 쿠폰 횟수 = 5
갯수 부족 횟수 = 15
- select for update가 바로 트랜잭션이 데이터에 접근할때부터 바로 Lock을 거는 쿼리이다.
select pessimisti0_.id as id
...
...
for update;
- Transaction A가 select ... for Update를 통해서 접근을 했다. 그래서 아예 바로 Lock을 건다.
- Transaction B, C, D는 애초에 접근하지 못한채 기다리고 대기한다.
- Transaction A가 이 자물쇠가 끝난 후 접근가능하다.
결론
- 낙관적락, 비관적 락을 각 상황에 맞게 적용해야한다.
'무언가에 대한 리뷰 > 테크영상리뷰' 카테고리의 다른 글
[무언가에 대한 리뷰][테크영상리뷰][쉬운코드] 데이터베이스 트랜잭션(transaction)을 아십니까? 그리고 트랜잭션의 매우 중요한 속성들인 ACID를 아십니까? 모르신다면 들렀다 가시지요 (0) | 2023.12.04 |
---|---|
[무언가에 대한 리뷰][테크영상리뷰][10분 테코톡] 🐤 샐리의 트랜잭션 (0) | 2023.12.04 |
[무언가에 대한 리뷰][테크영상리뷰][프로그래머스데브코스]JPA N+1 문제 - 권주성 (0) | 2023.10.12 |
[무언가에 대한 리뷰][테크영상리뷰][10분 테코톡] 잉, 페퍼의Spring Data JPA 삽질일지 (0) | 2023.10.12 |
[무언가에 대한 리뷰][테크영상리뷰][10분 테코톡] 무민의 JVM Stack & Heap (0) | 2023.10.11 |