Engineering Note

[SW Engineering] 동시성 문제 테스트 환경 구축기 본문

SW Engineering

[SW Engineering] 동시성 문제 테스트 환경 구축기

Software Engineer Kim 2025. 9. 9. 17:22

동시성 문제 테스트 환경 구축기

문제 정의

상품 주문 로직을 개발하다 보면 여러 사용자가 동시에 주문을 하거나, 한 사용자가 네트워크 오류·중복 클릭 등으로 동일한 주문이 여러 번 요청될 수 있습니다. 이런 경우 재고가 음수로 내려가거나, 중복 주문이 발생하는 장애가 생길 수 있습니다.

하지만 JUnit 테스트에서 이런 동시 요청 상황을 시뮬레이션할 도구가 마땅치 않아, 직접 간단한 테스트 도구를 구현하기로 했습니다.


해결 방법

JUnit 환경에서 Thread Pool을 활용해 멀티쓰레딩 동시 요청을 구현했습니다.

ExecutorService executor = Executors.newFixedThreadPool(10);
  • ExecutorService를 이용해 직접 new Thread()를 만들지 않고 쓰레드 풀 방식으로 작업을 실행했습니다.
  • newFixedThreadPool(10)의 의미는 최대 10개의 쓰레드를 만들어 재사용한다는 뜻입니다.
  • 예를 들어 100개의 주문 요청을 동시에 보내더라도, 내부적으로는 동시에 최대 10개 요청만 실행됩니다.
  • 나머지 요청은 큐에 쌓였다가 쓰레드가 비면 순차적으로 실행됩니다.

즉, 100개의 요청을 보내면 10개의 쓰레드 동시에 실행 → 종료되면 대기 중 요청이 차례로 실행되는 방식으로 동시 요청 환경을 만들 수 있습니다.


동기화 문제

동시 요청을 보낸 뒤에는 반드시 **주문 개수와 재고 차감 결과를 검증(assert)**해야 합니다.

문제는, 작업 쓰레드가 모두 끝나기 전에 메인 테스트 쓰레드가 검증을 실행할 수 있다는 점입니다. 이렇게 되면 아직 처리가 덜 끝난 상태에서 결과를 확인하게 되어, 테스트가 올바르지 않게 됩니다.

이를 해결하기 위해 CountDownLatch를 사용했습니다.

CountDownLatch latch = new CountDownLatch(요청개수);
  • 쓰레드가 작업을 마칠 때마다 latch.countDown()을 호출합니다.
  • 메인 쓰레드느 latch.await()을 통해 모든 쓰레드가 완료될 때까지 대기합니다.

이렇게 하면 모든 요청이 끝난 시점에만 검증 로직이 실행되므로, 동시성 처리 여부를 정확히 확인할 수 있습니다.


결과 및 구현 코드

  • 문제: 동시 요청 테스트 환경이 없어서 동시성 검증이 불가능했다.
  • 해결: JUnit + ExecutorService + CountDownLatch로 간단한 테스트 도구를 구현했다.
  • 효과: 원하는 만큼의 동시 요청을 보내고, 모든 요청이 끝난 시점에 정확히 검증할 수 있게 되었다.

 

   private int 주문_동시요청(int 요청개수) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(요청개수);
        AtomicInteger exceptionCount = new AtomicInteger(0);

        for (int i = 0; i < 요청개수; i++) {
            executor.submit(() -> {
                try {
                    orderService.placeOrder(userId, productId, 1);
                } catch (OutOfStockException e) {
                    e.printStackTrace();
                    exceptionCount.incrementAndGet(); // 재고 부족 예외 카운트
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executor.shutdown();
        return exceptionCount.get();
    }

 

 

 

Comments