MySQL InnoDB Repeatable Read, Row Lock - PESSIMISTIC_WRITE

shinny·2024년 7월 14일

[성능 개선]

목록 보기
2/8

1️⃣ 작성 코드

아래 코드를 보면, 쿠폰을 발행하는 issue 메서드가 있다.
먼저 쿠폰을 조회한 다음, 쿠폰의 발급 갯수를 ++1 해줄 것이다.
그런 다음, coupon id + user id를 함께 저장해주는 coupon issue 내역을 저장한다.
즉, 쿠폰 발행의 issue 메서드는 DB의 관점에서 SELECT, UPDATE, INSERT가 모두 발생하는 행위이다.

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

findCoupon(couponId) 메서드를 더 들여다보면, 쿠폰을 조회하는 query가 다음과 같이 작성되어 있다.

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

여기서 핵심은 SELECTPESSIMISTIC_WRITE이다.
데이터 조회 시점에서 Row Lock을 걸었다는 것이고, 이 락은 배타적 락으로서, 다른 트랜잭션에서의 읽기와 쓰기 모두를 제한하는 X-Lock(Exclusive Lock)이다.

2️⃣ Row Lock으로 충분한가?

이것은 메서드 호출 시, 일어나는 일과 발생 가능한 상황을 살펴보아야 답할 수 있는 질문이다.

메서드 호출 시, 크게 다음과 같은 일이 발생한다.

  • coupon data row의 issued quantity 값을 total quantity와 비교하고, 발행 가능한 상태라면 issued quantity 값에 1을 더한다.
  • coupon issue table의 1 row를 추가한다.

즉, 해당 row만 조회하는 것이고 index를 통해 조회하거나, 범위를 조회하는 일은 하지 않는다. 그렇기 때문에 Row Lock만 하면 되고, Record Lock, Gap Lock은 하지 않아도 된다.

3️⃣ 평가

  • 조회 시, 비관적 락인 X-Lock을 적용하는 것으로도 데이터 일관성을 해치는 동시성 이슈는 해결되었다. 하지만 문제는 MySQL이 뚜렷한 병목 지점이 된다는 것이다. MySQL이 병목 구간이 될 확률을 낮추기 위해서는 DB 서버의 스케일업 전략을 취하거나 혹은 DB를 분산해야하는데, DB 분산은 관리도가 꽤 높은 작업이다. 그렇기 때문에 조금 더 유지보수가 용이한 방법으로 동시성 이슈 해결 방법을 전환할 필요가 있다.

  • RPS : 138, Average Response Time : 6.5 secs

  • CPU usage : 74%

Reference

  • [DB] Lock 기반으로 트랜잭션 격리수준 이해하기(링크)
  • Lock의 종류 (Shared Lock, Exclusive Lock, Record Lock, Gap Lock, Next-key Lock)(링크)
  • [JPA] 낙관적 락과 비관적 락(링크)
profile
꾸준히, 성실하게, 탁월하게 매일 한다

0개의 댓글