Redis Lock + AOP를 이용한 동시성 제어

제이 용·2026년 1월 7일

왜 Redis Lock이 필요한가?

  • 문제 상황

    • 좌석 예매, 쿠폰 발급, 포인트 차감처럼
    • 여러 요청이 동시에 같은 자원을 수정하는 상황 발생
  • 단순 조회 후 수정 방식에서는 두 요청이 동시에 isSelected == false 확인

  • 동시에 true로 변경 → 중복 선택 발생

요구사항

  • 단 하나의 요청만 자원을 수정

  • 나머지는 즉시 실패 또는 대기


Redis Lock을 선택한 이유

  • Redis Lock 특징

    • 분산 환경에서도 동시성 제어 가능

    • DB Lock보다 빠르고 부담이 적음

    • 서버가 여러 대여도 하나의 Redis를 통해 락 공유


DB Lock만으로 부족한 이유

  • 트랜잭션 범위가 길어질수록 DB 부하 증가

  • API 서버 확장 시 제어 어려움

“분산 락”이 필요했기 때문에 Redis 선택


AOP로 Lock을 분리한 이유

  • 서비스 코드에 락을 직접 쓰면?
lock();
try {
    비즈니스 로직
} finally {
    unlock();
}

모든 메서드에 반복됨

  • 가독성 ↓

  • 실수로 unlock 누락 위험

  • AOP 적용 후

@RedisLock(key = ...)
public void businessLogic() { ... }
  • 락 로직 완전 분리

  • 비즈니스 로직에 동시성 코드 없음

  • 선언적으로 락 적용 가능

락은 횡단 관심사(Cross-cutting concern)

AOP로 분리하는 게 가장 적합

핵심 키워드 정리

  • AOP 관련

    • @Aspect

    • @Around

    • ProceedingJoinPoint

    • 횡단 관심사 (Cross-cutting Concern)

  • Redis Lock 관련

    • SETNX (setIfAbsent)

    • TTL (Time To Live)

    • 분산 락 (Distributed Lock)

    • Lua Script (원자적 해제)

  • 설계 관련

    • SRP (단일 책임 원칙)

    • DIP (의존성 역전 원칙)

    • 선언적 프로그래밍

    • 관심사 분리 (Separation of Concerns)


RedisLock 어노테이션 설계

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    String key();
}

왜 이렇게 설계했는가?

  • METHOD 단위 → 락은 로직 실행 범위에 적용

  • RUNTIME 유지 → AOP 실행 시 접근 필요

  • key를 SpEL로 받음 → 동적 락 키 생성


SpEL을 이용한 Lock Key 생성

@RedisLock(key = "'seat:' + #request.scheduleId + ':' + #request.seatNo")

SpEL을 쓴 이유

  • 메서드 파라미터 기반으로 자원 단위 락 구현 가능

  • 예시 결과

lock:seat:3:15

전체 좌석이 아닌 특정 좌석만 잠그는 최소 범위 락


RedisLockAspect의 역할

  • 역할 요약

    • 어노테이션 감지

    • SpEL 파싱

    • LockService 호출

    • 비즈니스 메서드 실행 제어

  • 핵심 흐름

    • @RedisLock 감지

    • Lock Key 생성

    • Redis Lock 획득

    • joinPoint.proceed() 실행

    • finally에서 락 해제


LockService의 책임 분리

public <T> T executeWithLock(String key, Supplier<T> action)
  • 책임

    • 락 획득 / 해제

    • TTL 설정

    • 락 실패 시 즉시 예외 처리

  • 왜 Supplier?

    • 비즈니스 로직을 함수로 전달

    • 락 범위를 명확히 제어


RedisLockRepository 구현 포인트

  • 락 획득
SET key value NX PX ttl
  • 한 요청만 성공

  • TTL로 데드락 방지

  • 락 해제 (Lua Script)

if redis.call('get', key) == value then
    del key
end

락을 건 주체만 해제 가능

안전한 분산 락 구현


왜 @Around를 사용했는가?

  • 락은 메서드 실행 전 필요

  • 메서드 실행 후 반드시 해제

  • try-finally 구조 필요

@Around만 가능


이 구조의 장점 정리

  • 비즈니스 코드가 깔끔
  • 락 적용 여부를 어노테이션으로 제어
  • 재사용 가능
  • 테스트/확장 용이

요약

Redis 기반 분산 락을 AOP로 분리하여

비즈니스 로직과 동시성 제어를 분리했고,

SpEL을 활용해 자원 단위의 최소 범위 락을 구현했다!!

공부할게 산더미!!!!!!!!!!!!!!!(;´д`)ゞ

2개의 댓글

comment-user-thumbnail
2026년 1월 7일

내일 정독해야지

1개의 답글