개요

이 포스트는 세 가지 동시성 제어 방식의 성능을 비교합니다: 락 없는 트랜잭션, Beta Lock, Redisson 분산락

테스트 환경

  • DB: H2 (MariaDB 모드)
  • 환경: 로컬
  • Redis: Embedded
  • 스레드 수: 32
  • 요청 횟수: 각 1회

1. 트랜잭션만 사용한 결과

트랜잭션 테스트

@Transactional
public void notUseLockTest(String lockName, Integer userId) {
    BeanPay beanPay = getBeanPay(1, Role.USER);

    final BeanPayDetail beanPayDetail = BeanPayDetail.ofCreate(
       beanPay, 1, 5000
    );
    final BeanPayDetail createBeanPayDetail =
       beanPayDetailRepository.save(beanPayDetail);
    beanPay.chargeBeanPayDetail(createBeanPayDetail.getAmount());
}

결과

트랜잭션만 사용하면 MySQL의 기본 격리 수준(Repeatable Read)에서 Lost Update 문제가 발생하여 데이터 정합성이 낮아집니다.

2. Beta Lock 사용

Beta Lock 테스트

@Transactional
public void betaLockTest(String lockName, Integer userId) {
    BeanPay beanPay =
       beanPayRepository.findBeanPayByUserIdAndRoleUseBetaLock(1, Role.USER);

    final BeanPayDetail beanPayDetail = BeanPayDetail.ofCreate(
       beanPay, 1, 5000
    );
    final BeanPayDetail createBeanPayDetail =
       beanPayDetailRepository.save(beanPayDetail);
    beanPay.chargeBeanPayDetail(createBeanPayDetail.getAmount());
}

특징

  • 높은 정합성 보장
  • 단일 서버 환경에서 최적화
  • Lock 진행 중 읽기 차단으로 조회 성능 저하 가능
  • 우수한 사용성

3. Redisson 분산락

Redisson 분산락 테스트

@DistributedLock(key = "#lockName.concat('-').concat(#userId)")
public void useDistributeLock(String lockName, Integer userId) {
    BeanPay beanPay = getBeanPay(1, Role.USER);

    final BeanPayDetail beanPayDetail = BeanPayDetail.ofCreate(
       beanPay, 1, 5000
    );
    final BeanPayDetail createBeanPayDetail =
       beanPayDetailRepository.save(beanPayDetail);
    beanPay.chargeBeanPayDetail(createBeanPayDetail.getAmount());
}

특징

  • 높은 정합성 보장
  • 분산 환경에 적합
  • Lock과 DB 분리로 조회 가능
  • 우수한 확장성

비교 요약

방식정합성성능환경특징
Repeatable Read빠름모든 환경데이터 무결성 미보장
Beta Lock매우 빠름단일 서버읽기 제한
Redisson빠름분산 환경확장성 우수

실제 환경 테스트

In-Memory 환경과 달리 실제 데이터베이스 환경에서는 Beta Lock과 Redisson 성능 차이가 감소하는 것으로 나타났습니다.

결론: 단일 서버면 Beta Lock, 분산 환경이면 Redisson 분산락을 선택하세요.