java의 Redis 클라이언트 중 하나인 Redisson을 활용하여 분산락을 적용
회사에서 서비스 중인 앱에 새롭게 사은품을 포인트로 구매할 수 있는 시스템이 도입되었다. 사은품에는 재고량이 정해져있었기에 멀티 스레드를 기본적으로 사용하는 Spring에서 동시성 문제가 발생할 수 있다고 생각하였다.
이를 확인하고자 코드를 모두 구현한 후 JMeter를 이용하여 부하테스트를 간단하게 진행해보았다.
테스트 조건: Thread 100개
테스트 전 재고 수
테스트 후 재고 수
위 사진을 보면 알 수 있듯이 100개의 Thread, 즉 100 명의 사용자가 동시에 주문 API를 호출했을 때 실제 재고는 26개 밖에 줄어 들지 않았다. (74% 유실)
동시성 문제가 발생하는 원인은 크게 4가지가 있다.
이 중 현재 발생하는 문제는 경합 조건(Race Condition)
이 원인으로 파악된다.
(시스템이 다운되지 않았고, 재고 감소와는 달리 주문 내역은 알맞게 100건이 DB에 저장되었음)
(추후 동시성 이슈 발생 원인 별도 정리 후 링크)
경합 조건으로 인해 동시성 이슈가 발생하는 경우 낙관적 락, 비관적 락, 분산 락 등 다양한 방법으로 이 문제를 해결할 수 있다. 때마침 프로젝트에 Redis가 사용되고 있기 때문에 Java 진영의 Redis Client 중 하나인 Redisson을 활용하여 분산 락을 적용하여 해결하기로 결정했다.
데이터의 원자성을 확보
대표적으로 Jedis, Lettuce와 Redisson 존재한다. 이 중 Jedis는 나머지 두 Client에 비해 상대적으로 부족한 성능과 분산락에 관한 래퍼런스가 없어 고려하지 않고 Lettuce와 Redisson의 장단점을 비교하여 최종적으로 Redisson
을 선택하였다.
java.util.concurrent.locks.Lock
인터페이스를 구현하고 있어 분산 환경에서도 일반 Java 락처럼 사용 가능프로젝트에 이미 Redisson 관련 설정이 모두 적용돼있기에 구성 환경 설정은 개인 프로젝트에서 정리 후 재포스팅 예정
기존 코드에 RLock을 적용하여 재구현해보았다.
@Transactional(rollbackFor = Exception.class)
public Long makeOrder(Long memberId, Long itemId, OrderInfoAndDeliveryHistoryDTO.SaveRequest request) {
log.info("MAKE ORDER :: {}", itemId);
RLock lock = acquireLock(itemId);
try {
return performOrderTransaction(memberId, itemId, request);
} finally {
releaseLock(lock);
}
}
private RLock acquireLock(Long itemId) {
RLock lock = redissonClient.getLock("itemLock:" + itemId);
try {
if (!lock.tryLock(10, 30, TimeUnit.SECONDS)) {
log.error("COULD NOT ACQUIRE A LOCK FOR ITEM :: {}", itemId);
throw new RuntimeException("COULD NOT ACQUIRE A LOCK FOR ITEM :: " + itemId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("LOCK ACQUISITION INTERRUPTED", e);
throw new RuntimeException("LOCK ACQUISITION INTERRUPTED", e);
}
return lock;
}
최종적으로 아래와 같이 데이터 유실없이 재고 감소가 Thread의 수인 100만큼 알맞게 줄어든 것을 확인하였다.
분산락 테스트 전 재고수
분산락 테스트 후 재고수
결과는 대만족이었으나 찝찝함이 많이 남았다. 앞서 얘기했던 것처럼 Redis에 관한 설정은 기존에 있는 것을 그대로 사용하였고, 아직 Redisson 내부적으로 RLock이 어떤식으로 동작하는지 깊이있게 알지 못하기에 이 부분을 더 공부하여 재정리한 포스팅을 작성해보아야겠다.