Spring

게시글 동시성 처리 - 비관적 락은 언제 써야 할까?

으엉어엉 2025. 10. 27. 19:22
728x90

비관적 락(Pessimistic Lock)이란?

비관적 락은 이름 그대로 **“비관적으로 본다”**는 개념이다.

“어차피 동시에 접근할 사람 있을 거야.
그러니 내가 수정하는 동안 다른 사람은 아예 접근하지 못하게 막자.”

즉,
데이터를 읽는 순간 DB 레벨에서 행(row)을 잠그고,
트랜잭션이 끝날 때까지 다른 트랜잭션이 접근하지 못하게 하는 방식이다.

SQL로는 이렇게 쓴다. 

SELECT * FROM item WHERE id = 10 FOR UPDATE;
 

이 쿼리가 실행되면,
id=10인 row는 해당 트랜잭션이 끝날 때까지 잠긴다.
다른 트랜잭션이 같은 row를 수정하려 하면 대기(block) 하거나 LockTimeoutException이 발생한다.


비관적 락이 꼭 필요한 상황

1) 재고 차감, 수량 관리

동시에 여러 명이 접근할 때, 재고가 음수로 떨어지면 안 되는 경우.

예시:
한정 수량 상품을 판매하는 쇼핑몰.

@Transactional
public void order(Long itemId) {
    Item item = itemRepository.findByIdWithLock(itemId) // FOR UPDATE
        .orElseThrow();
    if (item.getStock() <= 0) throw new SoldOutException();
    item.decreaseStock();
}

한 명이 재고를 차감하는 동안
다른 사람은 대기하게 된다.

 “같은 상품을 동시에 두 번 팔면 안 된다”는 종류의 문제에 적합.


2) 결제, 송금, 포인트 차감

1원의 오차도 허용되지 않는 정산, 회계, 금융 시스템.

  • 여러 요청이 동시에 들어와도
    잔액이나 포인트가 절대 잘못 계산되면 안 된다.
  • 낙관적 락으로 감지하고 재시도하는 것보다,
    아예 충돌 자체를 차단하는 게 안전하다.

예시:

SELECT * FROM account WHERE id = 1 FOR UPDATE;
UPDATE account SET balance = balance - 100 WHERE id = 1;

이렇게 하면 동시에 여러 송금이 와도
한 트랜잭션씩 순서대로 처리된다.


3) 선착순 이벤트, 쿠폰 발급, 좌석 예약

“한정된 자원을 동시에 여러 명이 가져가면 안 된다”는 경우.

  • 100장짜리 쿠폰 이벤트
  • 영화관 좌석 예매
  • 한정판 선착순 주문

이런 상황에서는
낙관적 락을 쓰면 충돌 후 재시도가 너무 빈번해지고 서버 부하가 커진다.
오히려 한 명씩 순차 처리하는 비관적 락이 더 안정적이다.


비관적 락의 단점 (주의해야 할 점)

비관적 락은 강력하지만 그만큼 비용이 크다. 

⚠️ 1) 트랜잭션이 길면 병목 발생

락을 잡고 있는 동안 다른 트랜잭션은 대기 상태가 된다.
즉,

락을 오래 쥘수록 서버 전체의 처리량(Throughput)이 떨어진다.

예시:

  • A 사용자가 row를 잠근 상태로 몇 초 동안 대기
  • B 사용자의 요청은 그 시간만큼 block
  • 대기 중인 요청이 쌓이면 DB Connection Pool이 가득 차고,
    서비스 전체가 느려질 수 있다.

그래서 반드시 짧은 트랜잭션 안에서만 사용해야 한다.


⚠️ 2) Deadlock 위험

락이 여러 테이블/row에 걸릴 경우,
트랜잭션 순서가 꼬이면 교착 상태(Deadlock)가 발생할 수 있다.

→ JPA에서는 @QueryHints 로 타임아웃을 걸어두는 습관이 좋다.

 

@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value = "3000"))
Optional<Item> findByIdWithLock(Long id);

 

728x90