ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • mysql을 활용한 동시성 문제 해결 - 1. Pessimistic Lock
    백엔드 관련 강의 공부/동시성 이슈 - 재고시스템 2024. 2. 3. 16:19

    mysql을 활용한 동시성 문제 해결

     

    1. Pessimistic Lock

    - 실제로 데이터에 Lock을 걸어서 정합성을 맞추는 방법이다. exclusive lock을 걸게되면 다른 트랜잭션에서는 lock이 해제되기 때문에 데이터를 가져갈 수 없게 되지만 데드락이 걸릴 수 있으므로 조심해야 한다.

    (데드락 : 두 개 이상의 프로세스나 스레드가 서로 상대방의 자원을 기다리면서 무한히 대기하는 상태)

     

    2. Optimistic Lock

    - 실제로 Lock을 이용하지는 않지만 버전을 이용함으로써 정합성을 맞추는 방법이다. 데이터를 읽고 update를 하기 전에 내가 읽은 버전이 맞는지 확인하며 업데이트 한다. 내가 읽은 버전에서 수정 사항이 생겼을 때에는 application에서 다시 읽은 후에 작업을 수행한다.

     

    3. Named Lock

    - 이름을 가진 metadata locking이다. 이름을 가진 lock을 획득한 후 해제할 때까지 다른 세션은 이 lock을 획득할 수 없다. 주의할 점은 transaction이 종료될 때 lock이 자동으로 해제되지 않기 때문에 별도의 명령어로 해제를 하거나 선정시간이 끝나야지 해제가 된다.

     

     

     

    1. Pessimistic Lock

    Thread-1이 lock을 걸면 다른 Thread는 lock이 풀릴 때까지 lock을 걸 수 없다.

     

    public interface StockRepository extends JpaRepository<Stock, Long> {
    
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        @Query("select s from Stock s where s.id = :id")
        Stock findByIdWithPessimisticLock(Long id);
    }

    StockRepository에 메서드를 생성하는데, @Lock 어노테이션을 이용하면 Pessimistic Lock을 걸 수 있다.

     

    @Service
    @RequiredArgsConstructor
    public class PessimisticLockStockService {
    
        private final StockRepository stockRepository;
    
        @Transactional
        public void decrease(Long id, Long quantity) {
            Stock stock = stockRepository.findByIdWithPessimisticLock(id);
    
            stock.decrease(quantity);
    
            stockRepository.save(stock);
        }
    }

    PessimisticLockStockService를 이용해서 감소시키는 로직을 만들었다.

     

     

    @SpringBootTest
    public class StockServiceTest {
    
        @Autowired
        private PessimisticLockStockService stockService;
    
        .... (생략)
    
        @Test
        public void 동시에_100개의_요청() throws InterruptedException {
            int threadCount = 100;
            ExecutorService executorService = Executors.newFixedThreadPool(32);
            // Excutors는 비동기로 실행하는 작업을 단순화해서 사용할 수 있게 해주는 자바의 API이다.
            CountDownLatch latch = new CountDownLatch(threadCount);
            // CountDownLatch는 다른 thread에서 수행중인 작업이 완료될 때까지 도와주는 클래스
    
            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(0, stock.getQuantity());
        }
    }

    테스트코드에서 의존성 주입받는 service를 StockService가 아니라 PessimisticStockService로 변경하고 테스트 코드를 실행시켰더니 테스트가 정상적으로 성공했다.

     

    쿼리문에 for update라는 것이 있는데, 이 부분이 lock을 걸고 데이터를 가져오는 것이라고 한다.

     

     

     

    장점

    • 충돌이 빈번하게 일어난다면 optimistic lock보다 성능이 좋다.
    • 데이터 정확성이 보장된다.

     

     

    단점

    • 별도의 lock을 잡기 때문에 성능 감소가 있을 수 있다.
Designed by Tistory.