[올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (3)

2024. 11. 26. 18:48·Project

이번 포스팅에선 네임드 락을 이용해 데드락을 해결하는 과정을 담았습니다.

 

이전 포스팅이 궁금하시다면 아래 링크를 참고해 주세요!

2024.11.25 - [Project] - [포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락 해결기 (1)

 

[포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락 해결기 (1)

이번 포스팅은 분석 서비스를 개발하다 만난 데드락을 해결하는 과정을 담았습니다.감정 분석 서비스 미리 보기기존 서비스에 이용자를 모으기 위해 고도화하는 프로젝트 중 하나가 `감정 분석

dev-gallery.tistory.com

2024.11.26 - [Project] - [포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락 해결기 (2)

 

[포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락 해결기 (2)

이번 포스팅에선 Testcontainers 내용이 잠깐 나옵니다.Testcontainers 환경을 구성하는 방법은 이전 포스팅을 참고해 주시면 됩니다.2024.09.16 - [Project] - [예약 대기 시스템] 4. 컨테이너 환경에서 테스트

dev-gallery.tistory.com

 

네임드 락에 대해

MySQL 엔진 레벨에서 제공하는 네임드 락은 임의의 문자열에 대해 잠금을 설정할 수 있습니다.

핵심은 잠금의 대상이 테이블이나 레코드같은 데이터베이스 객체가 아니라 단순히 지정한 문자열에 대해 `획득(Acquire)`과 `반납(Release)`하는 잠금입니다.

사용 방법

잠금 획득

-- 'lockName'에 대해 잠금을 획득, 만약 잠금이 사용 중이면 5초를 대기 (5초 이후 대기 해제)
SELECT GET_LOCK('lockName', 5);

잠금을 획득하면 1, 이미 잠금이 있으면 지정한 시간(초)만큼 대기합니다.

만약 대기 시간동안 잠금을 획득하지 못하면 0을 반환합니다.

잠금 확인

-- 잠금이 설정되어있지 않으면 1, 이미 잠금이 있으면 0 반환
SELECT IS_FREE_LOCK('lockName');

해당 문자열에 대해 잠금이 없으면 1, 잠금이 있으면 0을 반환합니다.

잠금 해제

-- 잠금 해제 성공 시 1, 그렇지 않으면 0을 반환
SELECT RELEASE_LOCK('lockName');

잠금 해제 성공 시 1, 실패 시 0을 반환합니다.

네임드 락 구현하기

JPA에서 `@Lock` 애너테이션은 낙관적 락과 비관적 락만 지원합니다.

따라서 네임드 락은 별도로 구현해야 합니다.

NamedLockRepository 구현

@Slf4j
@Repository
public class NamedLockRepository {

    @PersistenceContext
    private EntityManager em;

    @Transactional(propagation = Propagation.MANDATORY)
    public boolean acquireLock(String name, int timeoutSeconds) {
        String sql = "SELECT GET_LOCK(:name, :timeoutSeconds)";
        Object result = em.createNativeQuery(sql)
                .setParameter("name", name)
                .setParameter("timeoutSeconds", timeoutSeconds)
                .getSingleResult();

        log.info("[Acquire Lock] Thread Name={}, result={}", Thread.currentThread().getName(), result);
        return result != null && result.toString().equals("1");
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public boolean  releaseLock(String name) {
        String sql = "SELECT RELEASE_LOCK(:name)";
        Object result = em.createNativeQuery(sql)
                .setParameter("name", name)
                .getSingleResult();

        log.info("[Release Lock] Thread Name={}", Thread.currentThread().getName());
        return result != null && result.toString().equals("1");
    }
}

 

  • `EntityManager`를 주입받고, nativeQuery를 통해 네임드 락을 제어하도록 설계했습니다.
  • `@Transactional(propagation = Propagation.MANDOTORY)`: 구현하는 네임드 락은 동시성 제어를 목적으로 사용되고, 트랜잭션 일관성을 깨뜨리지 않아야 합니다. 따라서 락을 획득하고 반납하는 곳에 트랜잭션 전파 속성을 `MANDATORY`로 설정했습니다.

    `MANDOTORY` 속성은 기존 트랜잭션이 존재하지 않으면 `IllegalTransactionStateException` 예외가 발생합니다.
    호출하는 쪽에서 반드시 트랜잭션을 열어야하는 제약을 만들기 때문에 개발자가 실수할 확률을 줄여줄 수 있습니다.

AbstractPlatformTransactionManager 일부

네임드 락 적용으로 데드락 해결하기

네임드 락 적용

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class DailyReportService {

    // 기존 코드 생략..
    
    // 네임드 락 Repository 추가
    private final NamedLockRepository namedLockRepository;

    public DailyReportResponseDto createDailyReport(DailyReportDto.CreateRequest dailyReportDto) {
        // 네임드 락 획득
        String lockName = "createDailyReport";
        boolean lockAcquired = namedLockRepository.acquireLock(lockName, 2);

        // 네임드 락 획득 실패 시 예외 처리 (409 Conflict)
        if (!lockAcquired) {
            throw new NamedLockAcquisitionException("Failed to acquire lock:" + Thread.currentThread().getName());
        }

        // 네임드 락 획득 시 기존 로직 처리
        try {
            // 기존 코드 생략..
        } finally {
            // 네임드 락 반납
            boolean released = namedLockRepository.releaseLock(lockName);
            
            // 반납 실패 시 경고 로그 생성
            if (!released) {
                log.warn("Failed to release lock: {}", lockName);
            }
        }
    }
  • 락 획득에 실패하면 커스텀 예외인 `NamedLockAcquisitionException`를 던집니다.
    이는 `RestControllerAdvice`에서 `409 Conflict`로 처리합니다.

RestControllerAdvice에서 409 Conflict 처리

  • 락 획득에 성공하면 `try-finally` 안에서 정상 로직을 처리합니다.
    만약 데이터베이스 서버 오류로 락 반납에 실패하면 경고 로그를 생성합니다.

테스트 결과

테스트는 통과
데드락은 해결!

테스트는 모두 통과했습니다.

100개의 스레드에서 1개를 제외한 나머지 스레드는 예외를 발생시켰는데, 네임드 락을 획득하지 못한 예외와 데일리 리포트가 존재해서 발생하는 예외였습니다.

또 다른 문제

하지만 또 다른 문제가 생겼습니다. 데일리 리포트에 대해 `INSERT` 쿼리가 2번 나가는 문제입니다.

데일리 리포트는 1번만 생성되야하는데, 2번 생성되는 문제가 발생했습니다.

17시 7분 0초 567 밀리초
17시 7분 0초 616 밀리초

 

다음 포스팅에서 이 문제를 해결해보겠습니다.

다음 포스팅:

2024.11.27 - [Project] - [포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (4)

 

[포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (4)

이전 포스팅: 2024.11.26 - [Project] - [포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락 해결기 (3) [포텐데이 409-1pick] 분석 서비스 개발 중 만난 데드락 해결기 (3)이번 포스팅에선 네임드 락을 이

dev-gallery.tistory.com

 

긴 글 읽어주셔서 감사합니다.

'Project' 카테고리의 다른 글

올려 올려 라디오 NCloud 활용 후기  (2) 2024.11.27
[올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (4)  (2) 2024.11.27
[올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (2)  (0) 2024.11.26
[올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (1)  (0) 2024.11.25
[올려올려 라디오] 올려올려 라디오 서비스 개발기 - 2  (2) 2024.10.08
'Project' 카테고리의 다른 글
  • 올려 올려 라디오 NCloud 활용 후기
  • [올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (4)
  • [올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (2)
  • [올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (1)
옐리yelly
옐리yelly
전시회에서 도슨트를 따라다니며 작품 해설을 들으면 더 재밌었던 기억들이 있습니다. 글로 더 재밌는 개발이 되도록 노력하고 있습니다.
  • 옐리yelly
    개발 갤러리
    옐리yelly
  • 전체
    오늘
    어제
    • 모든 글 보기 (82)
      • Project (22)
      • Java (4)
      • Spring (6)
      • Kubernetes (6)
      • Docker (2)
      • JPA (2)
      • Querydsl (2)
      • MySQL (8)
      • ElasticSearch (7)
      • DevOps (4)
      • Message Broker (3)
      • Git & GitHub (2)
      • Svelte (1)
      • Python (8)
        • Python Distilled (4)
        • Anaconda (1)
        • Django (0)
        • pandas (3)
      • Algorithm (1)
      • Computer Science (0)
      • 내 생각 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    JPA
    프로젝트
    nks
    ncloud
    비사이드
    querydsl
    Python
    포텐데이
    OOP
    Message Broker
    리팩토링
    RabbitMQ
    elasticsearch
    성능 테스트
    argocd
    dataframe
    k8s
    pymysql
    blue-green 배포
    예약 시스템
    gitops
    Project
    MySQL
    데드락
    Spring
    pandas
    커넥션 풀
    svelte
    devops
    docker
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
옐리yelly
[올려올려 라디오] 분석 서비스 개발 중 만난 데드락과 동시성 문제 해결기 (3)
상단으로

티스토리툴바