SpringBoot 동시성 이슈 해결방법[3] - Lock

Euiyeon Park·2025년 2월 17일
post-thumbnail

🛒📦재고시스템으로 알아보는 동시성 이슈 해결 방법

Review

재고 감소 로직의 문제점

Race Condition

  • Race Condition여러 개의 프로세스 또는 스레드가 공유 자원에 동시 접근(변경) 할 때
    발생하는 문제로, 실행 순서에 따라 예상치 못한 결과가 발생하는 상태

해결 방법

  • 하나의 Thread가 작업이 완료된 이후에, 다른 스레드가 데이터에 접근할 수 있도록 해야 함

해결 방법1의 문제점

  • 재고 감소 로직의 문제점인 Race Condition을 해결하기 위해 첫 번째 해결 방법으로
    자바 synchronized 키워드를 사용했으나, synchronized는 몇 가지 한계점을 갖는다.

synchronizedd의 한계

  • 실제 운영 서비스에서는 대부분 스케일 아웃(Scale Out)을 통해 여러 대의 서버를 실행하고,
    로드밸런싱을 통해 부하를 분산하여 서비스를 제공한다.
  • synchronized하나의 프로세스 내에서만 동기화를 보장하므로
    다중 서버 환경에서 동기화를 보장할 수 없다.

✨ 해결 방법2 -MySQL Lock 활용

🔒 1. Pessimistic Lock(비관적 락)

  • 트랜잭션이 데이터에 접근할 때, 다른 트랜잭션이 동시에 접근하지 못하도록 선점적으로 락을 거는 방식
  • 실제 데이터에 직접적으로 락을 거는 방식
  • 다른 트랜잭션은 락이 해제되기 전까지 데이터를 읽을 수 없음

특징

  • 데이터 충돌을 피하기 위해 먼저 락을 걸고 작업을 진행
  • 일반적으로 Exclusive Lock(X Lock)을 사용해 다른 트랜잭션의 Read/Write을 방지 - 배타적 락
  • 충돌이 빈번한 환경에서 유용(은행 계좌이체, 재고 관리)
  • 락을 통해 업데이트를 제어하므로 데이터 정합성이 보장

단점

  • 락이 오래 유지될 경우 동시성 저하 - 성능 저하
  • 데드락(Deadlock) 발생 가능성 존재

사용 예시

  • FOR UPDATE는 해당 행에 X LOCK을 걸어 다른 트랜잭션의 Read/Write을 할 수 없도록 함
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- ♻️ 락이 걸린 상태에서 처리
UPDATE orders SET status = 'CONFIRMED' WHERE id = 1;
COMMIT;

🔒 2. Optimistic Lock(낙관적 락)

  • 데이터를 읽고 수정할 때까지 락을 걸지 않고, 수정 시점에서 충돌 여부를 확인
  • 실제로 락을 이용하지 않고 버전을 이용해 데이터 정합성을 맞춤

특징

  • 트랜잭션이 데이터를 읽고 수정할 때, 다른 트랜잭션이 변경하지 않았는지 확인 후 업데이트
  • 읽은 버전에 수정사항이 생겼을 경우 application에서 다시 읽은 후 작업 수행
  • 일반적으로 버전번호 또는 타임스탬프를 활용해 변경 여부를 확인
  • 동시성이 높은 환경에서 유용하며, 충돌 가능성이 낮은 경우 적합

단점

  • 충돌이 발생하면 다시 데이터를 읽고 재시도해야 함

사용 예시(Java + JPA)

  • 데이터 변경 시 version값이 증가하며, 업데이트 시 기존 버전과 비교해 충돌 감지
@Entity
public class Order{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // 🔒 낙관적 락 적용(JPA)
    @Version    
    private Integer version;
    
    private String status;
}
Order order = orderRepository.findById(1L).orElseThrow();
order.setStatus("CONFIRMED");

// ⚠️ 만약 버전이 다르면 Optimistic Lock 예외 발생
orderRepository.save(order); 

🔒 3. Named Lock(네임드 락)

  • 데이터베이스에서 특정한 이름을 가진 락을 획득해 동시성 제어
  • 이름을 가진 메타데이터 락
  • Pessimistic Lock은 row, table 단위지만 Named Lock은 메타데이터를 락킹
  • 먼 소리임?

NamedLock 상세 설명

  • NamedLock은 특정 이름을 기반으로 동기화하는 락으로,
    같은 이름을 가진 락을 획득하려는 스레드 간에 동기화를 보장하는 동시성 제어 방식
  • 일반적으로 synchronizedReentrantLock은 객체 자체에 대한 락을 걸지만,
    NamedLock은 이름을 기반으로 동기화
    • 같은 이름을 락을 사용한 스레드는 동시에 실행되지 않고, 순차적으로 실행
  • 분산 시스템(여러 서버 인스턴스)에서도 같은 이름의 NamedLock을 사용하면
    동일한 자원에 대한 동기화를 보장할 수 있다.

특징

  • 트랜잭션과 별개로 락을 걸 수 있음(특정 리소스를 보호할 때 사용)
  • 주로 MySQL의 GET_LOCK을 이용
  • 락을 걸 때 테이블의 특정 행이 아니라, 특정한 명칭(리소스 단위)에 락을 거는 방식

단점

  • 락을 획득하지 못하면 대기하거나 재시도 로직 필요
  • 트랜잭션 종료 시 락 자동 해제❌, 락을 걸고 해제하는 별도 로직이 필요

사용 예시(MySQL)

-- 🔒 락 획득(5초 동안 대기하여 획득)
SELECT GET_LOCK('order_lock', 5);

-- ♻️ 처리 로직
UPDATE orders SET status = 'CONFIRMED' WHERE id = 1;

-- 🔓 락 해제
SELECT RELEASE_LOCK('order_lock');
profile
"개발자는 해결사이자 발견자이다✨" - Michael C. Feathers

0개의 댓글