쿠폰 중복 사용 버그, 비관적 락 한 줄로 해결하기

2025. 11. 21. 17:49·Loopers 2기

쿠폰 중복 사용 버그, 비관적 락 한 줄로 해결하기

문제 상황

주문 API를 만들고 테스트하던 중 이상한 현상을 발견했다.

동시에 여러 요청이 들어오면 같은 쿠폰이 2번 사용되는 버그가 발생했다.

분명히 쿠폰 사용 여부를 체크하는 로직이 있는데, 왜 이런 일이?

원인 분석

문제는 이랬다.

시간 →

스레드 A: 쿠폰 조회 (사용 가능!) → 사용 처리
스레드 B:      쿠폰 조회 (사용 가능!) → 사용 처리

두 스레드가 거의 동시에 조회하면 둘 다 "사용 가능"으로 판단한다. 그리고 둘 다 사용 처리를 해버린다.

이게 바로 Lost Update 문제다.

해결: 비관적 락

해결은 간단했다. 조회할 때 락을 걸면 된다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("""
    SELECT mc FROM MemberCoupon mc
    JOIN FETCH mc.coupon c
    WHERE mc.memberId = :memberId
    AND mc.coupon.id = :couponId
    AND mc.deletedAt IS NULL
""")
fun findByMemberIdAndCouponIdWithLock(memberId: String, couponId: Long): MemberCoupon?

@Lock(LockModeType.PESSIMISTIC_WRITE) 한 줄 추가했다.

이제 흐름이 이렇게 바뀐다:

시간 →

스레드 A: 쿠폰 조회 (락 획득) → 사용 처리 → 락 해제
스레드 B:      대기...              → 조회 (이미 사용됨!) → 실패

서비스 코드

fun getMemberCoupon(memberId: String, couponId: Long): MemberCoupon? {
    couponRepository.findByIdOrThrow(couponId)

    // 비관적 락으로 조회
    return memberCouponRepository.findByMemberIdAndCouponIdWithLock(memberId, couponId)
        ?: throw CoreException(ErrorType.COUPON_NOT_FOUND, "쿠폰을 보유하고 있지 않습니다")
}

락이 걸린 메서드를 호출하면, 트랜잭션이 끝날 때까지 다른 스레드는 해당 row에 접근하지 못한다.

왜 비관적 락?

낙관적 락도 고려했지만, 쿠폰은 비관적 락이 맞다고 판단했다.

구분 비관적 락 낙관적 락
방식 조회 시 락 수정 시 버전 체크
충돌 시 대기 예외 → 재시도
적합한 경우 충돌 많음, 실패 비용 큼 충돌 적음

쿠폰 사용은:

  • 한 번 실패하면 UX가 나빠짐 (결제 실패)
  • 동시 요청 가능성 있음 (여러 기기에서 동시 주문)
  • 재시도보다 대기가 나음

그래서 비관적 락을 선택했다.

정리

  • 동시성 문제는 단일 스레드 테스트로는 발견 안 됨
  • 비관적 락은 @Lock(LockModeType.PESSIMISTIC_WRITE) 한 줄
  • 쿠폰처럼 실패 비용이 큰 경우 비관적 락이 적합

'Loopers 2기' 카테고리의 다른 글

재고 차감, 검증은 몇 번 해야 할까?  (0) 2025.11.14
Round 2 회고  (0) 2025.11.07
Round 1 회고  (0) 2025.10.31
'Loopers 2기' 카테고리의 다른 글
  • 재고 차감, 검증은 몇 번 해야 할까?
  • Round 2 회고
  • Round 1 회고
고구마와 감자
고구마와 감자
Amor DevFati는 김연자-Amor Fati에 Development(개발)의 Dev 를 첨가하여 만든 이름
  • 고구마와 감자
    Amor DevFati(아모르 개발파티)
    고구마와 감자
  • 전체
    오늘
    어제
    • 분류 전체보기 (156)
      • Loopers 2기 (4)
      • 스프링 (5)
      • 알고리즘 (113)
        • 백준 (70)
        • 프로그래머스 (7)
        • 인프런_자바코테강의 (20)
        • 리트코드 (5)
        • 해커랭크 (0)
        • 코드업 (3)
        • 이것저것 (7)
      • 자바 (7)
      • GIT (0)
      • 파이썬 (1)
      • 개발이론 (4)
      • JPA (0)
      • 김영한 강의 (13)
        • 모든 개발자를 위한 HTTP 웹 기본 지식 (2)
        • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (6)
        • 스프링 핵심 원리 - 기본편 (5)
      • 일기 및 아무말 적기 (6)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    Mini Fantasy War
    5361
    3059
    홀수일까 짝수일까
    2의 제곱인가
    등장하지 않는 문자의 합
    2921
    할로윈의 사탕
    5988
    고려대학교에는 공식 와인이 있다
    첫 글자를 대문자로
    스프링 핵심 원리
    전투 드로이드 가격
    11023
    남욱이의 닭장
    그대로출력하기2
    16673
    더하기 3
    카이사르 암호
    2857
    11966
    10178
    꼬리를 무는 숫자 나열
    14656
    조교는 새디스트야!!
    10409
    1598
    1247
    백준
    4458
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
고구마와 감자
쿠폰 중복 사용 버그, 비관적 락 한 줄로 해결하기
상단으로

티스토리툴바