JAVA/프로젝트

쿠폰 선착순 발급 이벤트 - 동시성 이슈 해결. MySql lock

whyHbr 2024. 11. 30. 21:16
728x90
반응형

MySql 에서는 레코드에 락을 걸 수 있다. 이것을 X락, Exclusize Lock, 쓰기락 이라고 한다. 

트랜잭션이 끝날 때 까지 읽기, 또는 쓰기를 실행 할 수 없다. 

X락은 중첩해서 걸 수 없다. 앞 트랜잭션이 끝날 때 까지 대기해야한다.

트랜잭션이 커밋 되는 순간 락이 해제된다.  


public interface CouponJpaRepository extends JpaRepository<Coupon, Long> {

    @Lock(LockModeType.PESSIMISTIC_READ) 
    @Query("SELECT c FROM Coupon c WHERE c.id = :id")
    Optional<Coupon> findCouponWithLock(Long id);
}

 

JpaRepository 에 락을 적용시켜준다.

public class CouponIssueService {

 @Transactional
    public void issue(long couponId, long userId) {
        Coupon coupon = findCouponWithLock(couponId);
        coupon.issue();
        saveCouponIssue(couponId, userId);
    }

 @Transactional(readOnly = true)
    public Coupon findCouponWithLock(long couponId) {
        return couponJpaRepository.findCouponWithLock(couponId).orElseThrow(() -> {
            throw new CouponIssueException(COUPON_NOT_EXIST,
                    "쿠폰 정책이 존재 하지 않는다. %s".formatted(couponId));
        });
    }

service 로직에 적용 시킨다.

 

public class CouponIssueRequestService {

    private final CouponIssueService couponIssueService;
    private final DistributeLockExecutor distributeLockExecutor;

    public void issueRequestV1(CouponIssueRequestDto requestDto) {
        couponIssueService.issue(requestDto.couponId(), requestDto.userId());
        log.info("쿠폰 발급 완료. couponId: {}, userId: {}", requestDto.couponId(), requestDto.userId());
    }
}

레디스 락을 해제시켜준다.

 


테스트 진행:

redis 를 활용할 때보다 RPS 가 향상 되었다. 


테스트 결과

coupon 발급이 300개가 되었고

coupon_issue table에 레코드가 300개 들어갔다. 

728x90