Database

룰렛 서비스에서 Redisson 분산 락 적용기

성시니 2025. 3. 12. 21:04
반응형

 

최근 진행한 프로젝트에서 룰렛 이벤트의 백엔드 개발을 담당했다.

 

이 중 제한된 수량의 상품 당첨 시 재고를 정확히 감소시키는 로직이 핵심 과제였다.

동시다발적인 요청에서도 데이터 무결성을 유지하기 위해 Redisson을 활용한 분산 락을 적용했으며, 이를 통해 재고 감소 처리 과정에서 발생할 수 있는 경합 및 동시성 이슈를 효과적으로 해결했다.

Lettuce 또한 Redis와의 통신 및 분산 환경에서 유용한 기능을 제공하지만, 프로젝트 요구사항 및 실제 운영 환경에서의 적합성을 고려해 Redisson을 선택했다.

 

Redisson을 사용한 이유

  1. 백엔드 서버가 14대 운영 중이므로 분산 환경에서 동시성 제어가 필요함
  2. RDB는 1대만 운영 중으로 락으로 인한 부하를 줄일 필요가 있음
  3. 이미 Redis 서버가 구축이 되어있음
  4. Redisson이 제공하는 락 기능을 활용하면 Redis에서 안정적으로 락을 관리할 수 있음.

Redisson 적용 테스트

Redisson을 사용하여 분산 락을 관리하는 커스텀 어노테이션을 구현하고, 이를 다양한 도메인에서 재사용할 수 있도록 설계하였다.

 

문제상황

Redisson을 활용한 테스트 과정에서 다음과 같은 이슈가 발생했다.

  • 테스트 코드는 정상적으로 동작하며, 락의 획득과 반환도 의도대로 이루어졌다.
  • 그러나 최종 남아있는 재고 수량이 예상과 달랐다.

예를 들어, 재고가 1000개인 상품에 대해 500명의 사용자가 동시에 1개씩 구매를 시도했을 때, 테스트 완료 후 남아있는 재고는 500개가 되어야 하지만 실제로는 515개, 525개 등으로 불일치가 발생했다.

 

원인분석

디버깅 결과, 트랜잭션이 커밋되기 전에 락 키가 반환되는 문제가 원인이었다. 이는 재고 감소 후 save 대신 saveAndFlush를 사용하여 해결할 수 있었지만, 이를 원하지 않았다.

따라서 별도의 클래스를 만들어 트랜잭션이 종료된 후에 락을 반환하도록 수정하였다.

 

전체 소스코드

https://github.com/DevSung/RedisLock

 

Entity와 Model 생성

 

 

RedissonConfig 생성

 

DistributedLock 생성

 

DistributedLockAspect 생성

비즈니스 로직 실행은 LockTransactionSynchronizer 클래스에 전달

전달된 비즈니스 로직은 executeWithTransactionSync 메서드를 통해 실행된다.

 

Lock Key를 생성하기 위해 호출되는 메서드에서 인자값을 추출하기 위해 validateArgs, extractId 메서드 사용

 

- validateArgs : 메서드 인자가 최소 하나 이상인지 확인하고, 그렇지 않으면 예외 발생

- extractId: 인자 배열에서 지정된 인덱스의 `Long` 타입 ID를 추출

- tryAcquireLock: DistributedLock에 정의된 분산락 설정값

 

LockTransactionSynchronizer 생성

이 클래스는 Redisson의 `RLock`을 사용하여 락을 획득하고, 스프링 트랜잭션과의 동기화를 통해 트랜잭션 완료 시에 락을 해제하는 역할을 수행

  •  RLock : 분산락을 관리하기 위한 Redisson Rlock 객체
  • executeWithTransactionSync : 트랜잭션 동기화가 활성화되어 있는지 확인한 후, 활성화되어 있으면 트랜잭션 완료 시 락을 해제하도록 동기화를 등록

 

ProductService 생성

@DistributeLock 선언 후 key Name 지정

DistributeLock 내부 paramIndex default 값이 0이기 때문에 첫 번째 인자인 `productId`가 락 키 생성에 사용

만약 메서드의 매개변수 위치가 변경되거나, 다른 매개변수를 락 키로 사용해야 하는 경우, `paramIndex` 값을 명시적으로 지정하여 사용

 

Test Code

 

Test 결과

스레드 풀 초기화 시 생성된 스레드 출력

 

모든 스레드 작업 완료 후 최종 재고 수량

 

반응형

'Database' 카테고리의 다른 글

[Mysql] 기존 테이블에 Primary key, Foreign key 추가하기  (0) 2021.12.09