본문 바로가기

spring

동시에 재고 감소 요청이 들어왔을 때의 문제와 해결 방법

문제 상황

가정: 재고가 100개 있는데 재고를 1개씩 줄이는 요청이 100개가 동시에 들어옴.

기대 결과: 모든 요청이 처리되어 재고가 0이 되어야 하지만, race condition으로 인해 0이 되지 않을 수 있음.

Race Condition

  • Race condition은 여러 스레드나 프로세스가 공유 자원에 동시에 접근하려고 할 때 발생하는 문제.
  • 스레드 간 경합으로 인해 예상치 못한 결과가 발생할 수 있음.
  • 예시: 동시에 재고 100개를 1씩 줄이는 요청이 들어오면 각각의 스레드가 100을 가져가서 재고 1개를 줄이는 작업을 수행하면 결과는 99가 됨.

해결 방법

  1. Lock 사용
    • 공유 자원에 대한 접근을 하나의 스레드만 허용하는 방법.
    • Java에서는 synchronized 키워드를 사용하여 구현 가능.
    • 단일 서버에서는 동작할 수 있으나, 여러 서버에서는 여전히 문제가 발생할 수 있음.
    • 성능 저하 가능성 있음.
  2. Pessimistic Lock
    • 데이터에 실제로 Lock을 걸어서 동시 접근을 제어.
    • 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 함.
    • 데드락 주의 필요.
    • 병행성이 높을 때 성능이 개선될 수 있음.
    • 단점 : 별도의 lock 을 잡기 때문에 성능 감소가 있을 수 있음.
  3. Optimistic Lock
    • Lock을 사용하지 않고 버전 정보를 이용하여 데이터의 변경 여부를 확인.
    • 업데이트 시 버전이 맞지 않으면 실패 후 재시도 필요.
    • 업데이트 실패 시 재시도 로직을 개발자가 직접 작성해야함.
    • 성능이 높을 수 있으며 충돌이 드물 때 적합.
  4. Named Lock
    • 획득한 락의 이름을 사용하여 다른 세션과 구분됨.
    • 획득한 락을 해제하지 않으면 다른 세션은 해당 락을 획득할 수 없음.
    • 트랜잭션이 종료되더라도 자동으로 락이 해제되지 않음.
    • 락을 획득한 후 일정 시간이 지나야 해제됨.
  5. Redis를 활용한 방법
    • Redis를 활용하여 Lock을 구현할 수 있음.
    • Redisson 라이브러리를 사용하면 편리하게 구현 가능.
    • Pub-Sub 방식으로 Lock 해제 알림 가능.
  6. Lettuce를 이용한 Lock
    • Lettuce는 Redis를 위한 자바 클라이언트 라이브러리.
    • Spin Lock 방식으로 구현되어 동시에 많은 스레드가 Lock 획득 대기하면 Redis에 부하가 올 수 있음.
  7. Redisson을 이용한 Lock
    • Redisson은 Redis 기반의 분산 객체와 서비스를 위한 라이브러리.
    • Lock 획득 재시도 등을 내부에서 처리해줌.
    • Pub-Sub 방식으로 Redis 부하가 적음.
    • 별도의 라이브러리를 사용해야 하므로 사용법을 익히는데 시간이 소요됨.

선택과 고려 사항

  • Lettuce나 Redisson을 사용하려면 Redis 인프라를 구축해야 함.
  • MySQL은 이미 사용 중이라면 추가 비용 없이 활용 가능하며 어느 정도의 트래픽까지는 문제 없을 수 있음.
  • 실무에서는 재시도가 필요한 경우 Redisson을 활용하고, 그 외에는 Lettuce 등을 고려할 수 있음.

 

code : https://github.com/78planet/stock-concurrency