Spring Data Redis는 org.springframework.data.redis.cache
패키지에서 Spring Framework의 캐시 추상화(Cache Abstraction)에 대한 구현을 제공한다. 레디스를 캐시 저장소로 사용하려면, 설정에 RedisCacheManager
를 추가해야 한다.
RedisCacheManager
의 동작은 RedisCacheManager.RedisCacheManagerBuilder
를 사용해 설정할 수 있다. 이를 통해 아래와 같은 설정이 가능하다.
- 기본
RedisCacheManager
지정 - 트랜잭션 처리 방식 설정
- 사전 정의된 캐시 구성
RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()) // 레디스 기본 캐시 설정
.transactionAware() // 트랜잭션 처리 방식 설정
.withInitialCacheConfigurations(Collections.singletonMap("predefined",
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues())) // 사전 정의된 캐시 구성
.build();
RedisCacheManager
가 생성하는 RedisCache
의 동작은 RedisCacheConfiguration
으로 정의된다. 이 설정을 통해 아래와 같은 항목을 구성할 수 있다.
- 캐시 키의 만료 시간(TTL)
- 키 접두사 설정
- RedisSerializer 구현체 지정: 객체를 레디스의 이진 저장 형식으로 변환하거나 역직렬화할 때 사용
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(1))
.disableCachingNullValues();
RedisCacheManager
는 기본적으로 lock-free 방식의 RedisCacheWriter
를 사용하여 레디스에 바이너리 값을 읽고 쓰며, 이를 통해 처리량을 향상한다. 하지만 이 방식은 putIfAbsent
이나 clean
처럼 여러 레디스 명령이 조합되는 작업에서는 원자성이 보장되지 않아 명령 충돌이 발생할 수 있다. 예를 들어, putIfAbsent
는 "존재하지 않을 경우에만 저장"이라는 동작을 구현하기 위해 두 번 이상의 명령을 레디스 보내야 하며, 이 과정에서 다른 스레드의 개입이 있을 수 있다.
락은 개별 캐시 항목이 아닌 캐시 전체에 적용된다. 즉, 하나의 키에 대한 락이 걸리는 것이 아니라, 해당 캐시 이름 전체에 대해 락이 걸려 중복 명령 실행을 방지한다.
RedisCacheManager cacheManager = RedisCacheManager
.builder(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory))
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
...
기본적으로 캐시 항목의 모든 키 앞에 실제 캐시 이름과 ::
가 접두사로 붙는다. 이 동작은 정적 접두사 및 계산된 접두사로 변경할 수 있다.
// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixCacheNameWith("(͡° ᴥ ͡°)");
The following example shows how to set a computed prefix:
// computed key prefix
RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);
캐시 구현은 기본적으로 KEYS
와 DEL
명령어를 사용하여 캐시를 작제한다. 그러나 KEYS
명령어는 키 공간이 클 경우 성능 문제를 유발할 수 있다. 따라서 RedisCachWriter
는 BatchStrategy
를 사용하여 SCAN
기반 배치 전략으로 전환할 수 있다. SCAN
전략을 사용할 경우, 레디스 명령의 왕복 횟수를 줄이기 위해 배치 크기를 설정해야 한다.
RedisCacheManager cacheManager = RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
...
KEYS 배치 전략은 모든 드라이버 및 레디스 운영 모드(독립형, 클러스터형)를 사용하여 완벽하게 지원된다. SCAN은 Lettuce 드라이버를 사용할 때 완벽하게 지원된다. Jedis는 클러스터링 되지 않은 모드에서만 SCAN을 지원한다.
캐시 만료
Spring Data Redis의 캐시 구현은 캐시 항목에 대한 TTL(Time-to-Live) 만료를 지원한다. 사용자는 고정된 기간 또는 캐시 항목당 동적으로 계산된 기간으로 TTL 만료 시간 제한을 구성할 수 있으며, 새로운 RedisCacheWriter.TtlFunction
인터페이스의 구현을 제공하면 된다. 모든 캐시 항목이 설정된 기간 후에 만료되어야 하는 경우 다음과 같이 고정 기간으로 TTL 만료 시간 제한을 구성하면 된다:
RedisCacheConfiguration fiveMinuteTtlExpirationDefaults =
RedisCacheConfiguration.defaultCacheConfig().enableTtl(Duration.ofMinutes(5));
그러나 TTL 만료 시간 초과가 캐시 항목에 따라 달라져야 하는 경우 RedisCacheWriter.TtlFunction
인터페이스의 사용자 지정 구현을 제공해야 한다:
enum MyCustomTtlFunction implements TtlFunction {
INSTANCE;
@Override
public Duration getTimeToLive(Object key, @Nullable Object value) {
// compute a TTL expiration timeout (Duration) based on the cache entry key and/or value
}
}
그런 다음 다음을 사용하여 전역 단위로 고정 기간 또는 캐시 항목별 동적 기간 TTL 만료를 구성할 수 있다:
글로벌 고정 기간 TTL 만료 시간 초과:
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
.build();
캐시 항목별로 동적으로 계산되는 전역, 기간 TTL 만료 시간 초과:
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(MyCustomTtlFunction.INSTANCE);
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaults)
.build();
글로벌 고정 기간 TTL 만료 시간 초과:
RedisCacheConfiguration predefined = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(MyCustomTtlFunction.INSTANCE);
Map<String, RedisCacheConfiguration> initialCaches = Collections.singletonMap("predefined", predefined);
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
.withInitialCacheConfigurations(initialCaches)
.build();
Time-To-Idle (TTI) Expiration
레디스 자체는 진정한 유휴 시간 만료(TTI) 개념을 지원하지 않는다. 하지만 Spring Data Redis의 캐시 구현을 사용하면 유휴 시간 만료(TTI) 만료와 유사한 동작을 구현할 수 있다. Spring Data Redis의 캐시 구현에서 TTI의 구성은 명시적으로 활성화, 즉 옵트인해야 한다. 또한, 위의 레디스 캐시 만료에서 설명한 대로 고정 기간 또는 TtlFunction
인터페이스의 사용자 정의 구현을 사용하여 TTL 구성도 제공해야 한다.
@Configuration
@EnableCaching
class RedisConfiguration {
@Bean
RedisConnectionFactory redisConnectionFactory() {
// ...
}
@Bean
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.enableTimeToIdle();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaults)
.build();
}
}
레디스 서버는 TTI에 대한 적절한 개념을 구현하지 않기 때문에 만료 옵션을 허용하는 레디스 명령으로만 TTI를 달성할 수 있다. 레디스에서 "만료"는 엄밀히 말해 TTL(Time-to-Live) 정책이다. 그러나 현재 Spring Data Redis의 Cache.get(key)
연산에서와 같이 키 값을 읽을 때 TTL 만료가 전달되어 TTL 만료 시간제한을 효과적으로 재설정할 수 있다. RedisCache.get(key)
는 레디스의 GETEX 명령을 호출하여 구현된다.
레디스 GETEX 명령어는 Redis 6.2.0 이상에서만 사용할 수 있다.
GETEX는 문자열(String) 타입의 키에서만 동작한다.
TtlFunction(Cache) vs LockTtiFunction (Lock)
Spring Data Redis #2792
락(Lock)의 TTL은 모든 캐시에서 고정되어 있는 반면, 항목(TTL/TTI)은 각 캐시 단위로 설정된다. 즉, 기본 설정을 사용하여 모든 캐시에 동일한 TTL/TTI를 적용할 수도 있고, 또는 캐시별로 TtlFunction
을 제공하여 TTL을 개별적으로 설정할 수도 있다:
RedisCacheManager.builder(…)
.withCacheConfiguration("myCache",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(…))
.build();
락(lock)은 RedisCacheWriter
와 관련되어 있으므로, 락 설정은 이 레벨에서만 적용 가능하다. 다시 말해, RedisCacheManager
가 관리하는 모든 레디스 캐시에 대해 락을 적용하거나 전혀 적용하지 않는 방식 중 하나를 선택해야 한다. 좀 더 세밀한 제어가 필요하다면, 커스텀한 RedisCacheWriter
를 만들거나 CacheManager
를 라우팅 가능한 형태로 만들어 캐싱 호출을 조건에 따라 분기하는 방식으로 구현할 수 있다.
'개발' 카테고리의 다른 글
Spring Data Redis Pipelining 정리 (1) | 2025.05.21 |
---|---|
Many to Many Relations (0) | 2025.05.19 |
Spring Data Redis Serializers 정리 (0) | 2025.05.16 |
선언형과 명령형 프로그래밍 정리 (0) | 2025.05.15 |
이벤트 소싱 정리 (0) | 2025.05.14 |