본문 바로가기
개발/Spring

[Spring] 동시성 이슈 해결 방법 (1)

by devhooney 2022. 9. 20.
728x90

간단한 재고 시스템으로 알아보는 동시성 이슈

 

Stock

@Entity
public class Stock {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long productId;

    private Long quantity;

    @Version
    private Long version;

    public Stock(){

    }

    public Stock(Long productId, Long quantity) {
        this.productId = productId;
        this.quantity = quantity;
    }

    public Long getQuantity() {
        return quantity;
    }

    public void decrease(Long quantity) {
        if (this.quantity - quantity < 0) {
            throw new RuntimeException("foo");
        }
        this.quantity = this.quantity - quantity;
    }
}

 

StockRepository

public interface StockRepository extends JpaRepository<Stock, Long> {

    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select s from Stock s where s.id = :id")
    Stock findByIdWithPessimisticLock(Long id);

    @Lock(value = LockModeType.OPTIMISTIC)
    @Query("select s from Stock s where s.id = :id")
    Stock findByIdWithOptimisticLock(Long id);
}

 

StockService

@Service
public class StockService {
    private StockRepository stockRepository;

    public StockService(StockRepository stockRepository) {
        this.stockRepository = stockRepository;
    }

    @Transactional
    public void decrease(Long id, Long quantity) {
        // get stock
        // 재고 감소
        // 저장

        Stock stock = stockRepository.findById(id).orElseThrow();

        stock.decrease(quantity);

        stockRepository.saveAndFlush(stock);
    }
}

 

Test코드

@SpringBootTest
class StockServiceTest {
    @Autowired
    private StockService stockService;

    @Autowired
    private StockRepository stockRepository;

    @BeforeEach
    public void before() {
        Stock stock = new Stock(1L, 100L);

        stockRepository.saveAndFlush(stock);
    }

    @AfterEach
    public void after() {
        stockRepository.deleteAll();
    }

    @Test
    public void stock_decrease() {
        stockService.decrease(1L, 1L);

        // 100 - 1 = 99
        Stock stock = stockRepository.findById(1L).orElseThrow();

        assertEquals(99, stock.getQuantity());

    }

    @Test
    public void 동시에_100개의_요청() throws InterruptedException {
        int threadCount = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                try {
                    stockService.decrease(1L, 1L);
                } finally {
                    {
                        latch.countDown();
                    }
                }
            });
        }

        latch.await();

        Stock stock = stockRepository.findById(1L).orElseThrow();

        // 100 - (1 * 100) = 0
        assertEquals(0L, stock.getQuantity());

    }
}

 

- 하나의 요청이 있을 경우에는 문제 없이 진행되지만, 여러 요청이 있을 경우 스레드가 꼬이면서 제대로 된 결과가 나오지 않는다.

- 이를 Race Condition이라고 하는데, 좀더 자세한 설명은

 

race condition이란 두 개 이상의 프로세스가 공통 자원을 병행적으로(concurrently) 읽거나 쓰는 동작을 할 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 같지 않고 달라지는 상황을 말한다.


 

- 예상한 흐름

쓰레드1 재고 쓰레드2
select * from stock where id = 1 id: 1, quantity: 5  
update set queantity = 4
from stock where id = 1
id: 1, quantity: 4  
  id: 1, quantity: 4 select * from stock where id = 1
  id: 1, quantity: 3 update set queantity = 3
from stock where id = 1

 

 

- 실제 흐름

쓰레드1 재고 쓰레드2
select * from stock where id = 1 id: 1, quantity: 5  
  id: 1, quantity: 5 select * from stock where id = 1
update set queantity = 4 
from stock where id = 1
id: 1, quantity: 4  
  id: 1, quantity: 4 update set queantity = 4 
from stock where id = 1

 

- 이러한 문제를 해결하기 위해서는 데이터에 하나의 쓰레드만 접근이 가능하도록 하면 된다!

 

 

- 강의 수강 후 정리내용입니다.

https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

 

재고시스템으로 알아보는 동시성이슈 해결방법 - 인프런 | 강의

동시성 이슈란 무엇인지 알아보고 처리하는 방법들을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

728x90