개발놀이터

JPA의 동시성 컨트롤 (낙관적 락, 비관적 락) 본문

JPA/JPA

JPA의 동시성 컨트롤 (낙관적 락, 비관적 락)

마늘냄새폴폴 2023. 6. 19. 21:13

이번 포스팅에선 JPA가 어떻게 동시성 문제를 컨트롤하는지에 대해서 포스팅해보겠습니다. 

 

개인적으로 동시성 문제는 애플리케이션에서 일어날 수 있는 가장 최악의 버그라고 생각합니다. 그 이유로는

 

  1. RuntimeException이 발생하지 않는다. 
  2. 컴파일러도 모른다. 
  3. 심각한 장애를 야기한다. (재고가 1개 있는데 3명이 1개씩 주문하면 재고가 0이 되면서 결제 로그가 세개 찍힘)

때문에 이러한 동시성 문제에 대해 개발자가 반응하고 테스트 케이스를 통해 동시성 문제를 잡아내야한다고 생각합니다. 물론 쉽지는 않지만요. 

 

JPA에선 이러한 동시성 문제를 해결하기 위해 두 가지 Locking 매커니즘을 제공합니다. 이제 본격적으로 시작해보죠!

 

Locking 매커니즘

JPA는 Locking 매커니즘으로 두 가지를 제공하는데 바로 낙관적 락 (Optimistic Lock) 과 비관적 락 (Pessimistic Lock) 입니다. 두 매커니즘 모두 특장점이 있는데요. 각각에 대해서 알아보고 언제 어떤 것을 사용해야 하는지 알아보죠. 

 

낙관적 락 (Optimistic Lock) 

이름에서도 알 수 있듯이 동시성 문제에 대해서 굉장히 낙관적인 편입니다. 그렇다고 실제로 동시성 문제가 발생해도 낙관적으로 아무것도 안해서 동시성 문제가 발생하는건 아니구요. 

 

쉽게 설명하면 충돌이 일어날 때만 Lock을 건다는 의미입니다. 

 

우선 동시성 문제가 발생하는 상황을 예시로 설명해보겠습니다. 

 

엘리스와 밥은 서로 레몬을 주문하는 상황입니다. 

 

  1. 서로 레몬을 검색한다. 
  2. 재고가 5개 남았다는 결과를 받는다. 
  3. 밥은 레몬을 두 개 주문하고 엘리스는 레몬을 네 개 주문한다. 
  4. 재고가 -1이 된다. 

사실 위와같은 예제는 현실에서 일어나지 않습니다. 그 이유는 몇 가지가 있는데

 

  1. 멀티 스레드 환경이다. 
    1. 각각 재고가 5개인 상태를 조회
    2. 밥은 레몬을 2개 주문, 엘리스는 레몬을 4개 주문
    3. 위의 그림대로 밥이 먼저 레몬을 주문해서 레몬의 재고가 3으로 변경
    4. 하지만 엘리스의 트랜잭션에서 레몬은 아직 5개 
    5. 그러므로 엘리스가 업데이트를 하는 순간 레몬의 재고가 1로 변경
  2. 멀티 스레드 환경이 아니다. 
    1. 싱글 스레드로 움직인다면 위와 같은 상황이 발생할 수 있습니다. 
    2. 하지만 보통 재고가 0으로 내려가면 Exception을 발생시킬 수 있습니다. 
    3. 때문에 위와 같은 예제는 발생하지 않습니다. 

 

1번의 상황인 멀티스레드 환경에서는 JPA는 각각의 스레드에 서로 다른 트랜잭션을 부여하기 때문에 각 트랜잭션들은 데이터베이스의 격리수준에 의해 서로 다른 값을 바라보고 있습니다. 

 

커밋을 넣자마자 바로 다른 트랜잭션에서의 재고에 영향을 미치지 않습니다. 

 

일어나지 않는 것과 별개로 일어난다고 가정하고 이야기를 풀어보겠습니다. (위의 예제가 가장 적절했습니다.)

 

낙관적 락은 버저닝 (Versioning) 을 사용해서 동시성 문제를 해결합니다. 

 

 

  1. 밥과 엘리스가 레몬을 조회한다. 재고 = 5, 버전 = 1
  2. 밥이 레몬 두개를 주문한다. 재고 = 3, 버전 = 2
  3. 엘리스가 버전 1의 레몬을 업데이트하려고 한다. 
  4. Exception 발생!

이렇게 버전이 맞지 않으면 예외가 발생하게 되어있습니다. 

 

버저닝은 @Version 어노테이션을 이용합니다. 참고로 패키지는 javax.persistence에 있습니다. (스프링 부트 3.x 이상은 jakarta.persistence) 

 

 

비관적 락 (Pessimistic Lock) 

비관적 락은 동시성 문제가 무조건 발생하는 것을 가정하고 락을 걸어버립니다. 보통 읽기 연산에 락을 걸어서 데이터의 일관성을 보장합니다. 

 

조금이라도 일찍 접근한 사람에게 커밋 권한이 주어지고 조금이라도 늦게 도착한 사람은 뒤에서 기다려야합니다. 

 

보통 Spring Data JPA를 사용한다면 @Lock 어노테이션으로 비관적 락을 걸 수 있습니다. 

 

 

 

3. 그래서 어떤 락을 써야하지?

 

  • 낙관적 락 : 커밋 시 충돌이 일어나면 OptimisticLockException이 발생하여 해당 예외를 처리함으로써 동시성 문제를 해결할 수 있다. 
    • 성능이 비관적 락에 비해 좋음
    • 충돌이 빈번하지 않은 경우에 사용하면 좋음
  • 비관적 락 : 무조건 읽기 작업에 락을 걸어 동시성 문제를 해결할 수 있다. 
    • 성능이 좋진 않음
    • 충돌이 빈번한 경우에 사용하면 좋음
    • 위의 예시에서처럼 반드시 동시성 문제가 발생하지 않아야 하는 상황에서 사용하면 좋음

 

 

마치며

이번 포스팅에선 JPA의 동시성 문제 컨트롤에 대해서 알아봤습니다. 이 개념에 대해서는 제가 알고 있었던 개념이었고 실제로 낙관적 락과 비관적 락을 고민했었던터라 크게 어려움 없이 공부할 수 있었습니다. 

 

여러분도 만약 프로젝트를 진행하다 동시성 문제가 발생할 것 같으면 JPA의 두 가지 Locking 매커니즘을 사용하는 것이 어떨까요? 

 

긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요~

 

 

출처

https://www.baeldung.com/jpa-optimistic-locking

 

https://serengetitech.com/tech/pessimistic-and-optimistic-locking-in-jpa-and-hibernate/

 

Pessimistic and Optimistic Locking in JPA and Hibernate | Serengeti

In this article, we’ll learn what the optimistic and pessimistic locking mechanisms are. They both represent mechanisms for handling simultaneous access to data. Additionally, we’ll learn when to use one or the other mechanism. When dealing with transa

serengetitech.com

https://swathisprasad.medium.com/concurrency-and-locking-with-jpa-everything-you-need-to-know-6b1111bca3b2

 

Concurrency and Locking with JPA: Everything You Need to Know

Imagine you have a system used by multiple users, where each user is trying to modify the same entity concurrently. How do you ensure that…

swathisprasad.medium.com