Mutex: 웹 개발에서의 동시성 제어 이해 및 활용

이번 글에서는 웹 애플리케이션에서 자주 부딪히는 동시성 제어 문제를 해결하기 위해 사용하는 Mutex의 개념과 활용 방안을 살펴보려고 합니다. 먼저 학습 목표를 정리하고, 실제 사례를 통해 왜 동시성 제어가 필요한지 이해한 뒤, Java/Spring 환경 예제와 함께 다뤄보겠습니다.


1. 학습 목표

  • Mutex의 기본 개념 이해
  • 웹 개발 환경에서 동시성 제어가 필요한 이유 파악
  • 비관적 락낙관적 락의 이해

2. 동시성 제어가 필요한 이유

웹 애플리케이션은 다수의 사용자 요청을 동시에 처리합니다.
이 과정에서 race condition이 발생하면, 예컨대 재고가 1개 남았음에도 두 건의 주문이 동시에 처리되어 재고가 음수로 떨어질 수 있습니다.
실제 쇼핑몰에서 재고 이중 주문 문제가 발생하면 서비스 신뢰도가 크게 떨어지므로, 이를 예방하기 위해 동시성 제어는 필수적입니다.


3. Mutex

3.1. 개념

Mutex(Mutual Exclusion)
한 번에 하나의 스레드만 공유 자원(Critical Section)에 접근하도록 보장하는 락

3.2. 데드락(교착상태)

  • 정의
    둘 이상의 스레드가 서로 다른 락을 획득한 상태에서, 상대가 가진 락을 얻기 위해 무한 대기하는 상황
  • 예시 시나리오
    1. A 스레드가 A 락 획득
    2. B 스레드가 B 락 획득
    3. A 스레드는 A 락을, B 스레드는 B 락를 기다리며 교착
  • 예방 기법
    1. 락 순서 일관화: 모든 스레드가 동일한 순서로 락 획득
    2. 타임아웃: 일정 시간 내 획득 실패 시 롤백 또는 재시도
    3. 알고리즘:데드락을 해소하기 위한 여러 알고리즘 존재

4. 웹 개발에서의 Mutex 활용

4.1. 비관적 락 (Pessimistic Lock)

  • 개념
    데이터 충돌 가능성이 높다고 보고, DB 수준에서 레코드를 즉시 잠금
    @Transactional
    public void orderProduct(Long productId, int qty) {
      // DB 레코드에 즉시 쓰기 락
      Product p = em.find(Product.class, productId, LockModeType.PESSIMISTIC_WRITE);
      if (p.getStock() < qty) {
          throw new SoldOutException();
      }
      p.decreaseStock(qty);
    }
    

    4.2. 낙관적 락 (Optimistic Lock)

  • 개념
    충돌 가능성이 낮다고 가정하고, 데이터를 읽은 시점과 커밋 시점의 버전(version) 정보를 비교하여 동시성 충돌을 검증합니다.
    충돌 시 OptimisticLockException이 발생하며, 이를 잡아 재시도 로직을 구현할 수 있습니다.

  • 엔티티 정의 예시
      @Entity  
      public class Product {  
          @Id  
          private Long id;  
          private int stock;  
          @Version  
          private Long version;  // 버전 필드  
          // getters/setters, decreaseStock() 등  
      }
    
  • 서비스 메서드 예시
      @Transactional  
      public void orderProduct(Long productId, int qty) {  
          // 1) 데이터 조회  
          Product p = repo.findById(productId)  
                          .orElseThrow(() -> new EntityNotFoundException("상품 없음"));  
          // 2) 재고 검증  
          if (p.getStock() < qty) {  
              throw new SoldOutException("재고 부족");  
          }  
          // 3) 재고 차감  
          p.decreaseStock(qty);  
          // 4) 커밋 시점에 version 검사 → 충돌 시 OptimisticLockException 발생  
      }
    

5. 결론

  • Mutex의 역할
    • 한 번에 하나의 스레드만 공유 자원에 접근하도록 보장해 데이터 정합성을 확보합니다.
    • 데드락이 발생하지 않도록, 락 순서 일관화·타임아웃 등으로 예방해야 합니다.
  • 비관적 락 vs 낙관적 락
    • 비관적 락(Pessimistic Lock): 충돌 가능성이 높을 때 DB 레코드를 즉시 잠가 안전하지만 DB 성능 저하
    • 낙관적 락(Optimistic Lock): 충돌 빈도가 낮을 때 버전 검증으로 가볍게 처리하되, 충돌 시 재시도 로직 필요
  • 적용 전략
    1. 트래픽·충돌 빈도에 따라 락 방식을 선택
    2. 분산 환경에서는 Redis 기반 분산 락 도입 고려
    3. 성능과 가용성 사이 균형을 위해 필요한 곳에만 락을 최소화하여 적용

이상입니다. 감사합니다.