동시성 이슈를 테스트 코드로 잡아보기 (작성중)

김태훈·2일 전
0

실무에서 동시성 이슈를 MongoDB의 Document를 활용한 분산락을 사용해서 제어하는 개발건을 맡아 진행했다. 본디 Redis를 쓰는게 정배이긴 하지만, 상황이 여의치 않아서 MongoDB로 진행하였다.
그런데 내가 개발했던 건에서 잘못된 try-catch-finally 사용으로 중복제어 에러를 반환하면서 바로 Lock을 해제해버리는 바람에 동시성 이슈가 제대로 해소되지 않는 문제가 발생하는데..

상황 세부 설명

그래서 테스트 코드를 어떻게 작성해볼까?

public String delete(String id) throws InterruptedException {
    try{
        delayUtil.randomDelay();
        lockService.tryLock(id);

        // 비즈니스 로직 수행
        delayUtil.randomDelay();
        resourceRepository.deleteById(id);

    } catch (DuplicateKeyException e) {
        log.info("DUP ERROR in Thread = {}", Thread.currentThread().getName());
        return "Fail (Lock)";
    } finally {
        log.info("UNLOCK in Thread = {}", Thread.currentThread().getName());
        lockService.unLock(id);
    }

    return "Success";
}

이를 위한 테스트 코드

@Test
@DisplayName("삭제시 지연시간이 있을경우, 그리고 스레드가 한꺼번에 병렬로 실행될 경우 중복삭제가 일어난다")
void delete_withWeakLockLogicWithDelayAndParallel_shouldAllowMultipleDeletions() throws InterruptedException {
    int concurrency = 100;
    ExecutorService executor = Executors.newFixedThreadPool(concurrency);
    CountDownLatch allThreadsLatch = new CountDownLatch(1); // 모든 스레드를 일괄 시작
    CountDownLatch doneLatch = new CountDownLatch(concurrency); // 완료 대기
    List<String> results = Collections.synchronizedList(new ArrayList<>());
    delayUtil.setTestDelayMs(100);

    for (int i = 0; i < concurrency; i++) {
        executor.submit(() -> {
            try {
                allThreadsLatch.await();
                String result = userService.delete("shared-id");
                results.add(result);
            } catch (DuplicateKeyException e) {
                results.add("Duplication Exception");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                doneLatch.countDown();
            }
        });
    }

    allThreadsLatch.countDown(); // 모든 스레드 동시에 출발 -> for문 돌면서 순차적으로 진행하는 것을 막음 병렬화되는 느낌
    doneLatch.await(); // 모두 종료될 때까지 대기
    executor.shutdown();

    long successCount = results.stream().filter("Success"::equals).count();
    System.out.println(results.toString());

    assertThat(successCount)
            .as("Delay시간이 없으므로, 하나의 스레드만 삭제 성공 ")
            .isGreaterThan(1);
}

왜 중복이슈가 발생하지 않고 모두 정상적으로 Unlock 처리될까?

profile
기록하고, 공유합시다

0개의 댓글