스프링

낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)

khw7385 2025. 3. 19. 19:00

서론

동시성 문제를 해결하기 위한 방법 중 낙관적 락, 비관적 락에 대해서 알아본다.

자바 언어 차원에서 제공하는 synchronized, Lock 을 이용하는 방식은 단일 서버 환경에서 가능한 방법이고 다중 서버(분산 시스템) 환겨에서는 불가능한 방법이다. 이 때, 여러 대의 서버에서 동일 자원에 접근하는 경우, 동시에 한 개의 프로세스(혹은 쓰레드)만 접근 가능하도록 하기 위해 사용하는 Lock을 분산 락(Distributed Lock)이라 부른다.

 

분산락의 정의는 다음 두 가지가 존재한다.

1. 어플리케이션 서버가 여러 대인 경우,  이들간의 동시성 문제를 해결하기 위해 사용되는 Lock
2. 스케일 아웃된 DB 환경에서 동시성 문제를 해결하기 위해 사용되는 Lock

이번 포스트에서 설명할 낙관적 락과 비관적 락은 1번 경우에 대한 Lock 이다. 만약, 2번 경우에 대해서 동시성 문제를 해결하기 위해서는 다른 방법이 필요하다.

 

분산락을 구현하는 여러 방법

  • 비관적 락(Pessimistic Lock)
  • 낙관적 락(Optimistic Lock)
  • USER-LEVEL Lock(Named Lock)(MySQL 한정)
  • Redis
  • Zookeeper
  • ...

 

낙관적 락(Optimistic Lock)

DB의 락을 사용하지 않고 버전을 통해 데이터의 정합성을 맞추는 방법이다. 읽을 때 버전과 변경할 때 버전이 다르다면 예외가 발생한다. 커밋을 할 때 엔티티의 버전과 DB 레코드의 버전을 비교한다.

UPDATE BOARD
SET
    TITLE=?,
    VERSION=? (version 1 증가)
WHERE 
    ID=?
    AND VERSION=? (버전 비교, 즉 현재의 버전과 같은 버전이 있는지)
@Entity
public class SampleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Version
    private Long version;

    private String data;
}
@Repository
public interface SampleEntityRepository extends JpaRepository<SampleEntity, Long> {
    @Lock(LockModeType.Optimistic)
    Optional<SampleEntity> findById(Long id);
}

 

보통, 낙관적 락을 사용하는 경우 예외가 발생하면 재시도를 통해 해결한다.

@Transactional
@Retryable(
    retryFor = {ObjectOptimisticLockingFailureException.class},
    maxAttempts = 1000,
    backoff = @Backoff(100)
)
public void bizlogic(){
	...
}

 

발생하는 예외는 다음과 같다.

  • OptimisticLockException (JPA 예외)
  • StateObjectStateException (하이버네이트 예외)
  • ObjectOptimisticLockingFailureException (스프링 예외 추상화)

 

비관적 락

비관적 락은 DB의 X-LOCK 혹은 S-LOCK을 이용하여 데이터의 정합성을 맞추는 방법이다.

public interface SampleEntityRpeository extends JpaRepository<SampleEntity, Long>{
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<SampleEntity> findById(Long id);
}

 

비관적 락을 사용하는 경우 발생하는 예외는 다음과 같다.

  • PessimisticLockException (JPA 예외)
  • PessimisticeLockingFailureException (스프링 예외 추상화)

 

비관적 락의 락 타입은 다음과 같다.

1. PESSIMISTIC_WRITE:

  • 일반적으로 사용되는 옵션으로 데이터베이스에 X_LOCK(쓰기락, 배타락)을 걸 때 사용한다.
  • 데이터베이스 SELECT FOR UPDATE를 사용해서 락을 건다.
  • 락이 걸린 로우는 다른 트랜잭션이 수행될 수 없다.
  • NON-REPEATABLE READ 를 방지한다.

2. PESSIMISTIC_READ:

  • 데이터를 반복 읽기만 하고 수정하지 않은 용도로 락을 걸 때 사용한다.
  • 일반적으로 잘 사용되지 않는다.
  • 데이터베이스 대부분은 방언에 의해 PESSIMISTIC_WRITE로 동작한다.

 

 

 

 

어떨 때 사용하는 것이 좋을까?

비관적의 락의 경우

  • 동시성이 떨어져 성능 저하가 있어 읽기가 많이 이루어지는 데이터베이스에는 좋지 않다.
  • 서로 자원이 필요한 경우에, 로우 자체에 락이 걸려 있어 데드락이 발생할 가능성이 높다.
  • 데어터의 무결성이 중요하고, 충돌이 많이 발생하여 잦은 롤백이 있는 프로젝트에 사용하는 것이 좋다.

낙관적 락의 경우

  • 트랜잭션 충돌이 많아 복구 작업을 많이 해야 하는 로직이면 좋지 않다.
  • 데이터 충돌이 자주 일어나지 않을 것이라고 예상되는 시나리오에서 좋다.

 

참고 자료

https://velog.io/@hye_b/JPA-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD%EA%B3%BC-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD

 

[JPA] 비관적 락과 낙관적 락

[JPA] 비관적 락과 낙관적 락

velog.io

https://ttl-blog.tistory.com/1568

 

[Spring] 동시성 문제 해결방법 (2) - 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)

🤔 서론 이전 글에서 알아본 synchronized는 단일 서버 환경에서만 동시성 문제를 해결할 수 있었습니다. 이번 글에서는 다중 서버 환경에서 동시성 문제를 해결할 수 있는 방법에 대해 알아보도록

ttl-blog.tistory.com