Lock & JPA Lock

hznnoy·2025년 11월 2일

CS

목록 보기
20/24

Lock

여러 트랜잭션이 동시에 동일한 데이터에 접근할 때, 데이터 무결성과 일관성을 보장하기 위한 동시성 제어 메커니즘

데이터베이스나 애플리케이션에서 트랜잭션 간 충돌을 방지하기 위해 사용된다.


락의 필요성

데이터베이스는 동시에 여러 클라이언트의 요청을 처리한다.

만약 두 트랜잭션이 같은 데이터를 동시에 수정하려 한다면, 다음과 같은 문제가 발생할 수 있다.

  • Lost Update(갱신 손실) A와 B가 같은 데이터를 읽고, A가 먼저 수정한 뒤 커밋했지만, 이후 B가 이전 값을 덮어써서 A의 변경이 사라지는 경우
  • Dirty Read(더티 리드) A가 수정 중인 데이터를 B가 읽고, 이후 A가 롤백했을 때 B가 잘못된 값을 읽은 경우
  • Non-repeatable Read(비반복 읽기) 한 트랜잭션 내에서 같은 데이터를 두 번 읽었을 때, 다른 트랜잭션의 수정으로 인해 값이 달라지는 경우

이러한 문제를 방지하기 위해 락을 통해 접근을 제어한다.

Lock의 종류

낙관적 락(Optimistic Lock)

  • 트랜잭션 충돌이 드물 것이라 가정하고, 별도의 락을 걸지 않는다.
  • 데이터를 수정할 때 버전 정보를 확인하여 충돌 여부를 판단한다.
  • 충돌 시 예외를 발생시켜 트랜잭션을 재시도하게 한다.

특징

  • DB 수준에서 락을 걸지 않기 때문에 성능이 좋다.
  • 하지만 충돌이 잦은 환경에서는 재시도가 빈번해져 비효율적이다.

대표 사례: JPA의 @Version 사용

@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private int stock;

    @Version
    private int version;
}

JPA는 엔티티의 @Version 필드를 자동으로 관리하며, 업데이트 시 다음과 같은 쿼리를 수행한다.

update product
set stock = ?, version = version + 1
where id = ? and version = ?

version이 일치하지 않으면 OptimisticLockException이 발생하여 트랜잭션 충돌을 감지할 수 있다.

비관적 락(Pessimistic Lock)

  • 트랜잭션 충돌이 빈번할 것이라 가정하고, 데이터를 읽는 즉시 락을 건다.
  • 다른 트랜잭션이 해당 데이터를 수정하거나 읽지 못하도록 막는다.

특징

  • 충돌을 사전에 방지할 수 있다.
  • 락으로 인해 데드락(Deadlock)이나 성능 저하가 발생할 수 있다.

대표 사례: SQL의 SELECT ... FOR UPDATE

JPA에서도 @Lock 어노테이션을 이용하여 비관적 락을 설정할 수 있다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select p from Product p where p.id = :id")
Product findByIdForUpdate(@Param("id") Long id);

이는 다음 SQL과 동일한 효과를 낸다.

select * from product where id = ? for update;

낙관적 락 활용 예시

  • 재고 감소, 주문 수량 증가 등 충돌 가능성이 낮지만 중요한 데이터 무결성이 필요한 경우
  • 예외 발생 시 재시도 로직을 구현하여 복구 가능

비관적 락 활용 예시

  • 같은 자원에 대해 다수의 사용자가 자주 접근하는 경우
  • 결제 처리, 좌석 예약 등 경쟁이 빈번한 서비스 로직

주의할 점

  • 비관적 락은 DB 커넥션 점유 시간이 길어질 수 있으며, 데드락 위험이 존재한다.
  • 낙관적 락은 트랜잭션 재시도가 필요하므로, 애플리케이션 레벨에서 예외 처리 로직을 설계해야 한다.

JPA에서 Lock 전략

JPA는 표준적으로 다음의 LockModeType을 제공한다.

LockModeType설명
OPTIMISTIC버전 필드를 사용해 낙관적 락을 적용. 트랜잭션 커밋 시 버전 검증
OPTIMISTIC_FORCE_INCREMENT낙관적 락과 동시에 버전 값을 강제로 증가시킴
PESSIMISTIC_READ다른 트랜잭션의 쓰기 차단, 읽기는 허용
PESSIMISTIC_WRITE읽기와 쓰기 모두 차단 (SELECT FOR UPDATE)
PESSIMISTIC_FORCE_INCREMENT비관적 락과 동시에 버전 증가
NONE락을 사용하지 않음

Lock은 동시성 문제를 해결하기 위한 핵심 메커니즘으로, 시스템의 특성과 트래픽 패턴에 따라 낙관적 락과 비관적 락을 적절히 선택해야 한다.

낙관적 락은 높은 성능과 확장성을, 비관적 락은 강력한 일관성을 제공한다.

JPA는 이를 추상화하여 어노테이션과 LockModeType을 통해 쉽게 사용할 수 있도록 지원하므로, 도메인 로직의 성격에 맞는 락 전략을 선택하는 것이 중요하다.

profile
노력에는 지름길이 없으니까요

0개의 댓글