요약

  1. 참여하기 api 실행 시 데드락 발생
  2. 외래키 잠금 전파로 인해 참여 엔티티 추가 시 데드락 발생
  3. 외래키 제약 조건을 풀어버림

문제 상황

이전에 낙관적 락 방식을 사용하여 플로깅 참여하기 api 를 구현해 보았다.

플로깅 구인 게시판에 참여 인원을 카운트 하는 필드에 대해서 동시성 이슈가 발생하는 부분에 대한 해결법으로 낙관적 락을 사용하였다. 하지만 이 해결법 만으로는 주어진 문제를 완벽하게 해결하진 못했다.

참여 인원을 카운트 하는 것은 성공적 이었다.

하지만 궁극적으로 참여 인원 카운트 + 참여 인원 목록을 DB에 저장을 해야했다.

따라서 참여 를 관리하는 엔티티를 생성하여 연관관계를 걸어 두었으나, 참여하기 api 에 대해 테스트 해본 결과 데드락 문제가 발생하였다.

지난 이야기는 참여인원 카운트 관련 동시성 이슈 탐구 및 해결 를 확인 해보자.

아래 코드가 데드락이 발생하는 테스트 코드이다.


package mokindang.jubging.project_backend.recruitment_board.service;

import mokindang.jubging.project_backend.recruitment_board.domain.RecruitmentBoard;
import mokindang.jubging.project_backend.recruitment_board.repository.RecruitmentBoardRepository;
import mokindang.jubging.project_backend.recruitment_board.service.facade.OptimisticLockRecruitmentBoardResolver;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class RecruitmentBoardConcurrencyTest {

    //...

    @Test
    @DisplayName("동시성 문제 - 7명이 동시에 요청한 경우 게시글 카운트가 8이됨")
    void SuccessCountUp() throws InterruptedException {
        int threadCount = 7;
        ExecutorService executorService = Executors.newFixedThreadPool(32);

        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            int memberId = i + 2;
            executorService.submit(() -> {
                try {
                    recruitmentBoardService.participate((long) memberId, 1L);
                } catch (final Exception e) {
                    System.out.println(e.getMessage());
                } finally {
                    latch.countDown(); // 카운트
                }
            });
        }
        latch.await();
        RecruitmentBoard recruitmentBoard = recruitmentBoardRepository.findById(1L).get();
        assertThat(recruitmentBoard.getParticipationCount().getCount()).isEqualTo(8);
    }
}
//...

구현은 다음과 같이 하였다.