Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

이지은님의 블로그

250505 - Java Spring 도서 앱 구현: 주문 후 재고 감소의 동시성 제어(3) - 분산락 트러블 슈팅 본문

TIL

250505 - Java Spring 도서 앱 구현: 주문 후 재고 감소의 동시성 제어(3) - 분산락 트러블 슈팅

queenriwon3 2025. 5. 5. 23:20

▷ 오늘 배운 것

분산락 구현하면서 발생한 트러블 슈팅에 관해 포스팅해보겠다.

 

<<목차>>

1. 문제상황

2. 원인분석

3. 해결방법

4. 정리

 

 


 

 

1. 문제상황

주문시 재고를 감소하는 기능을 구현하고 분산락으로 재고감소의 동시성 문제를 해결하려고 했다. 이때 Redisson 분산락을 활용하여 동시성 문제를 방지하고 있었고, 다음과 같은 코드를 작성하였다

@Transactional
public void decreaseStockWithDistributedLock(Long bookId, int quantity) {
    String lockKey = "bookStockLock:" + bookId;

    redissonLockClient.runWithLockOrElse(lockKey, () -> {
        Book book = bookService.findBookByIdOrElseThrow(bookId);
        book.decreaseStock(quantity);
        bookRepository.saveAndFlush(book);
    }, () -> {
        throw new BadRequestException("현재 주문량이 많아 재고 확인에 실패했습니다.");
    });
}

테스트 코드에서는 여러 스레드가 동시에 재고를 감소시키는 것을 수행하는 시나리오를 작성했다.

하지만 모든 스레드가 재고 감소에 성공하지 못하는 현상이 발생했다. 이는 분산락이 의도대로 작동하지 않는 것으로 확인되었다.

 

 

 

2. 원인 분석

문제는 크게 두가지이다

 

❌ 트랜잭션 커밋 이전에는 DB에 반영되지 않음

  • @Transactional이 적용되어 있지만, 실제 DB 반영은 트랜잭션 커밋 시점까지 지연된다.
  • Redisson 락을 사용하는 동안 재고는 메모리(영속성 컨텍스트) 상에서만 감소한 상태이고, DB에는 반영되지 않기 때문에 다른 스레드가 동일한 재고 값을 가져가게 된다.

❌ saveAndFlush() 미사용으로 인한 지연 반영

  • book.decreaseStock(quantity)는 객체의 필드만 수정하며, 이를 DB에 즉시 반영하려면 saveAndFlush()가 필요하다.
  • 하지만 flush()가 호출되지 않았기 때문에 DB에는 아무런 변화가 발생하지 않았고, 락이 의미가 없어졌다.

 

즉, 락을 획득한 트랜잭션이 변경 사항을 DB에 즉시 반영하지 않아 동시성 문제가 발생한 것이다.

 

 

 

 

3. 해결 방법

@Transactional 대신 트랜잭션 내부에서 saveAndFlush() 명시적으로 호출하여 DB 즉시 반영하도록 수정했다.

public void decreaseStockWithDistributedLock(Long bookId, int quantity) {
    String lockKey = "bookStockLock:" + bookId;

    redissonLockClient.runWithLockOrElse(lockKey, () -> {
        Book book = bookService.findBookByIdOrElseThrow(bookId);
        book.decreaseStock(quantity);
        bookRepository.saveAndFlush(book);
    }, () -> {
        throw new BadRequestException("현재 주문량이 많아 재고 확인에 실패했습니다.");
    });
}

Redisson 락을 획득한 스레드는 saveAndFlush()를 통해 즉시 재고 감소 내용을 DB에 반영한다.

락이 해제되기 전에 DB 상태가 반영되어, 다른 스레드는 정확한 재고 값을 기반으로 작업하게 된다.

동시성 문제가 해결되고, 실제로 테스트에 분산락이 적용되어 테스트가 성공하는 결과를 얻을 있었다.

 

 

 

4. 정리

항목 설명
@Transactional 트랜잭션은 DB 반영을 커밋 시점까지 지연시킨다.
flush() 또는 saveAndFlush() 변경 사항을 즉시 DB 반영한다.
분산락 사용 주의 락을 사용하더라도, 내에서 DB 반영이 즉시 이루어지지 않으면 동시성 제어는 실패한다.

락을 사용한 로직에서는 saveAndFlush() DB 반영을 강제해야 동시성 문제가 해결된다.