[Redis] 스프링에서 레디스 사용하기 — 원자성을 보장하는 Redisson + Lua

y001·2025년 4월 19일
0
post-thumbnail

앞선 글에서는 Lettuce 기반의 RedisTemplateRedisRepository를 활용한 기본적인 Redis 사용법과 그 한계를 정리했다. 이번 글에서는 그 대안으로 자주 활용되는 RedissonLua 스크립트 조합을 소개한다.

이 조합은 특히 원자성 보장, 조건 분기, 복합 연산 처리가 필요한 경우 강력한 선택지가 된다. 실무에서 분산 락, RateLimit, 캐시 무효화 등 다양한 시나리오에 적용되는 구조이기도 하다.


왜 Redisson과 Lua인가?

1. 원자성 보장

RedisTemplate이나 RedisRepository는 단일 명령어 수준에서는 안전하지만, 여러 명령어를 조합해서 처리할 때는 Race Condition이 발생할 수 있다. 이를 해결하기 위해 Redis는 Lua 스크립트를 통한 원자 명령 실행을 지원한다.

2. 조건 분기 & 흐름 제어

Lua는 if, else, for 같은 제어문을 사용 가능하다. 이를 통해 Java에서는 구현이 번거로운 조건 로직을 Redis 내부에서 직접 처리할 수 있다.

3. Redisson이란?

Redisson은 Redis 기반의 고급 클라이언트 라이브러리로, 다음과 같은 기능을 제공한다:

  • 분산 락 (RLock)
  • 캐시 (RMapCache, RLocalCachedMap)
  • 분산 객체 (RList, RMap, RSet 등)
  • 원자 스크립트 실행 (RScript.eval())

Redisson은 단순히 Redis 명령을 보내는 도구가 아닌, 분산 환경에서 유틸리티 객체로 Redis를 사용하는 고급 API 집합이라 볼 수 있다.


Lua 스크립트를 사용하는 방식: RScript.eval()

Redisson은 다음과 같은 방식으로 Lua 스크립트를 실행한다.

val result = redissonClient.script.eval<Long>(
    RScript.Mode.READ_WRITE,
    luaScript,  // 문자열로 된 Lua 코드
    RScript.ReturnType.INTEGER,
    listOf("ratelimit:ip:127.0.0.1:minute", "ratelimit:ip:127.0.0.1:blocked"),
    limit.toString(),
    now.toString(),
    expireMillis.toString()
)

스크립트 실행은 단일 명령처럼 원자적으로 실행되며, 실행 도중 외부 간섭 없이 Redis에서 완결된다.


Lua 스크립트 문법 요약 및 내부 동작 원리

Lua는 Redis에 내장된 경량 스크립트 언어로, 다음과 같은 특징을 가진다:

  • KEYSARGV 배열을 통해 외부에서 전달된 값 접근
  • redis.call()을 통해 Redis 명령 호출 (예: SET, ZADD, ZREMRANGEBYSCORE 등)
  • 모든 명령은 Redis 서버 내부에서 직렬적(atomic)으로 처리

주요 redis.call 명령 예시

명령어설명
SET key value문자열 저장
GET key문자열 조회
EXPIRE key secondsTTL 설정
ZADD key score member정렬된 집합에 멤버 추가
ZREMRANGEBYSCORE key min max정렬된 집합에서 범위 삭제
ZCARD keyZSET 내 멤버 개수 조회
INCRBY key amount정수 증가
HSET key field value해시 필드 값 설정
HGET key field해시 필드 값 조회
DEL key키 삭제
LPUSH key value리스트 앞에 요소 추가
LPOP key리스트에서 요소 꺼내기
SADD key memberSET에 요소 추가
SCARD keySET 크기 확인

이 명령들은 Java에서 여러 번 나누어 호출할 수도 있지만, Lua 내부에서 한 번에 실행하면 원자적으로 처리되므로 예기치 않은 Race Condition을 막을 수 있다.

Redis는 해당 Lua 스크립트를 실행할 때 모든 명령을 큐에 밀어넣고 한 번에 처리하기 때문에 중간에 다른 요청이 끼어들 수 없다. 이로 인해 높은 동시성 환경에서도 일관성을 유지할 수 있다.


실무 활용 시 고려 사항

  • Redis 클러스터 환경에서는 Lua 스크립트를 실행할 수 없는 경우도 있다. (단일 키에 국한되도록 작성 필요)
  • 스크립트 캐싱(SHA1)을 활용하면 Lua 코드 재전송을 줄일 수 있다.
  • 실패 처리 시 로깅, 알림 등 백업 로직 필요 (ex. Redis 장애시 fallback 처리)

마무리

Redisson + Lua 조합은 Lettuce 기반의 기본 API로는 구현이 어려운 고급 흐름 제어를 Redis 내부로 위임할 수 있게 해준다.
특히 분산 환경, 조건 분기, 원자성 보장이 중요한 서비스에서는 이 패턴이 사실상 표준처럼 자리잡고 있다.

0개의 댓글