AOP 기반 Redis Lock 구현 및 고려사항

E.NO·2026년 3월 19일

1. AOP 기반 Lock 구조 적용

프로젝트에서 분산 락 처리를 비즈니스 로직과 분리하기 위해Spring AOP와 커스텀 애노테이션(@RedisLock)을 활용한 구조로 구현하였다.

@RedisLock 애노테이션을 메서드에 선언하면, AOP의 @Around 어드바이스가 동작하여 다음 흐름을 수행하도록 설계하였다.

  • Redis 락 획득 시도

  • 락 획득 성공 시 비즈니스 로직 실행 (joinPoint.proceed())

  • 실행 종료 후 finally 블록에서 락 해제

이를 통해 서비스 계층에서는 락 관련 코드 없이 비즈니스 로직에만 집중할 수 있도록 관심사를 분리하였다.

2. Lock Key 동적 추출 방식

분산 락은 동일 자원에 대한 동시 접근만 제어해야 하므로, 메서드마다 다른 리소스를 기준으로 락을 생성할 필요가 있다.

이를 해결하기 위해 본 프로젝트에서는 SpEL(Spring Expression Language)을 활용하여 Lock Key를 동적으로 생성하였다.

애노테이션 사용 예시는 다음과 같다.

@RedisLock(prefix = "event", key = "#eventId")
public void issueCoupon(AuthUser authUser, Long eventId)

AOP에서는 ProceedingJoinPoint를 통해 메서드의 파라미터 이름과 값을 추출한 뒤, StandardEvaluationContext와 SpelExpressionParser를 이용하여 #eventId, #request.eventId 등의 표현식을 실제 값으로 평가하였다.

최종적으로 생성되는 락 키는 다음과 같은 형태이다.

  • lock:event:1

  • lock:coupon:5

이와 같은 방식으로 리소스 단위로 락을 분리할 수 있으며, 서로 다른 이벤트나 쿠폰 요청이 불필요하게 서로를 блок킹하지 않도록 설계하였다.

3. 트랜잭션 경계와 락 처리 시점

AOP의 @Around 어드바이스에서는 락을 먼저 획득한 뒤 비즈니스 로직을 실행하는 구조로 구현하였다.

처리 흐름은 다음과 같다.

  1. Redis 락 획득 시도

  2. 락 획득 성공 시 joinPoint.proceed() 호출

  3. 내부에서 @Transactional이 적용된 서비스 로직 실행

  4. 메서드 종료 후 finally 블록에서 락 해제

즉,

락 획득 → 트랜잭션 시작 및 비즈니스 로직 실행 → 트랜잭션 종료 → 락 해제

순서로 동작하도록 구성하였다.

이러한 구조를 통해 동일 자원에 대한 동시 요청을
트랜잭션 시작 이전 단계에서 차단할 수 있으며,
데이터 정합성을 보다 안정적으로 보장할 수 있다.

4. 락 해제 안정성 확보

Redis 락은 DB 트랜잭션과 별개로 동작하기 때문에, 트랜잭션이 롤백되더라도 자동으로 해제되지 않는다.

이를 해결하기 위해 다음과 같은 방식을 적용하였다.

  • finally 블록에서 락 해제를 수행하여 예외 발생 여부와 관계없이 항상 해제

  • Lua 스크립트를 사용하여 현재 스레드가 획득한 락인지 검증 후에만 삭제

이를 통해 락 누수(lock leak) 및 잘못된 락 해제 문제를 방지하였다.

0개의 댓글