Spring & JPA

[테코톡] API 중복 호출 해결기

진진리 2025. 4. 2. 13:50

출처: https://www.youtube.com/watch?v=FEP2BzNzoRw&list=PLgXGHBqgT2TvpJ_p9L_yZKPifgdBOzdVH&index=24

 


문제 상황

  • 7주년 쿠폰 발급 행사
  • 1명의 사용자는 1개의 쿠폰을 발급 받아야 한다.
  • 여러 브라우저에서 동시에 동일한 API를 호출한다면?

* 프론트엔드 관련 내용 생략

 

 

서버에 검증 로직을 추가

  • validateAlreadyIssued 메서드를 통해 사용자가 쿠폰을 발급 받았는지 확인

-> DB에 한 사용자가 쿠폰을 여러 개 발급 받은 것을 확인!

 

2개 요청이 '동시'에 들어온다면?

  • 두 개의 메서드가 동시에 DB 트랜잭션을 시작
  • 발급 확인 메서드를 통과하게 됨

 

해결 방법

  • 자바 동기화 동구
  • 분산락
  • 유니크 제약 조건
  • 트랜잭션 격리 수준
  • select ... for share, for update 등

-> 자바 동기화 도구 (Synchronized 키워드), 분산락(MySQL 네임드락) 활용

 

Synchronized란? 

  • 특정 영역, 메서드에 [하나씩 들어와!] 하는 키워드
  • 암묵적 락
  • 자바 제공

 

Synchronized 활용

  • 쿠폰 발급 메서드에 synchronized 추가
  • 테스트 코드 작성 -> 여전히 2개
  • 이유
    • Synchronized가 끝나고 DB 트랜잭션이 커밋됨
    • @Transactional을 제거해서 해결 가능
  • 한계
    • 다른 사용자가 요청을 동시에 보냈을 때도 대기하게 됨 -> 비효율적
    • 서버가 여러 개인 경우 한 사용자가 쿠폰을 여러 개 발급 가능

 

분산락이란?

  • 다양한 프로세스가 공유 자원을 상호 배타적으로 사용하는 경우 유용한 도구

 

MySQL 네임드 락을 활용

  • MySQL에서 임의의 문자열에 잠금을 설정

  • 특징
    • 한 세션에서 잠금을 유지하고 있으면, 다른 세션에서 해당 잠금을 획득할 수 없다.
    • 획득한 잠금은 트랜잭션이 종료되어도 해제되지 않는다.
    • 현재 세션에서 획득한 잠금만 릴리즈할 수 있다.

 

MySQL 네임드 락 활용

  • tryLock: timeout초 만큼 락 획득 시도, 잠금
  • releaseLock: 네임드 락 잠금 해제
  • 같은 커넥션을 주입 받도록 구현
    1. 잠금을 획득한 커넥션과 다른 커넥션을 사용하여 릴리즈하면 실패
    2. 커넥션 풀링을 하는 경우 다른 스레드에서 락을 릴리즈 가능

  • 락 점유한 후 비즈니스 로직 실행
  • 데이터소스 분리
    • 커넥션 풀 데드락 발생할 수 있기 때문

 

고민해볼 지점

  • 데드락 발생
  • 추가 인프라 구축 비용 발생
  • 코드의 복잡도 상승 등

 

대안