๊ธ์ ๋ชฉํ
Spring Data Redis์์ @Cacheable ๋ฉ์๋๋ฅผ ์คํํ๊ณ , ์บ์ ๋ฏธ์ค(Cache miss)๋ก ์ธํด ๋ฐํ๊ฐ์ด ์บ์์ ์ ์ฅ๋๋ ๊ณผ์ . ๊ทธ๋ฆฌ๊ณ , @Transactional๊ณผ ํจ๊ป @CachePut/@CacheEvict๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ @CachePut/@CacheEvict๊ฐ ์ธ์ ์คํ๋๋์ง๋ฅผ ๋ด๋ถ ๊ตฌํ์ ํตํด ์๋ ๊ฒ์ด ๋ชฉํ์ ๋๋ค.
๋ณธ ๊ธ์ RedisCache์ ํ์ ๋ ๋ด์ฉ์์ ๋ฏธ๋ฆฌ ์๋ ค๋๋ฆฝ๋๋ค.
Spring Cache ์ถ์ํ ๊ตฌ์กฐ
Spring Cache๋ Cache ์ธํฐํ์ด์ค์ CacheManager ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ถ์ํ๋ฉ๋๋ค
This abstraction is materialized by the org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces.
Cache ์ธํฐํ์ด์ค
Cache ๋ Spring ์ด ์ง์ํ๋ ์ฌ๋ฌ Cache ๊ตฌํ์ฒด๋ฅผ ์ถ์ํํ ์ธํฐํ์ด์ค๋ก Redis,Caffeine๋ฑ ๋ค์ํ ์บ์๋ฅผ ์์ฝ๊ฒ ๊ต์ฒดํ ์ ์์ต๋๋ค. Cache์๋ get(), evict(), put() ๋ฑ์ ๋ฉ์๋๊ฐ ์์ผ๋ฉฐ, ์ด๋ ๊ฐ๊ฐ @Cacheable, @CacheEvict, @CachePut ์ด๋ ธํ ์ด์ ๊ณผ ๋งค์นญ๋๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค.

CacheManager ์ธํฐํ์ด์ค
CacheManager ๋ ์ด๋ฌํ Cache ๋ค์ ๊ด๋ฆฌํ๋ ์ธํฐํ์ด์ค์ ๋๋ค.
- getCache(name) : ํน์ ์บ์ ๊ฐ์ ธ์ค๊ธฐ
- AbstractCacheManager ๋ getCache ์์ Cache ๊ฐ ์์๊ฒฝ์ฐ, Cache ์ธ์คํด์ค๋ฅผ ์์ฑํ๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ , RedisCacheManager ๊ฐ AbstractCacheManager ๋ฅผ ์์๋ฐ๊ณ ์์ต๋๋ค.
- getCacheNames() : ์บ์ ๋ชฉ๋ก ์กฐํํ๊ธฐ

์ด ์ค, TransactionAwareCacheManagerProxy ๊ฐ ํธ์ถํ๋ TransactionAwareCacheDecorator.class ๋ฅผ ํตํด @Transactional ์์ @CacheEvict/Put์ ์คํ ์์ ์ ์์๋ณด๊ณ , RedisCacheManager.class ๋ฅผ ํตํด @Cacheable ๋์ ์๋ฆฌ๋ฅผ ์์๊ฐ ๋ด ๋๋ค.
RedisCacheManager
RedisCacheManager๋ Spring Data Redis ์์กด์ฑ์ ํฌํจํ ๊ฒฝ์ฐ ์ ๊ณต๋๋ ๊ธฐ๋ณธ ์บ์ ๋งค๋์ ์ ๋๋ค.
package org.springframework.data.redis.cache; // RedisCacheManager ์์น
RedisCachManager ์ ์ฃผ์ ์์ฑ๋ค์ ์์๋ณด๊ฒ ์ต๋๋ค.
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
protected static final boolean DEFAULT_ALLOW_RUNTIME_CACHE_CREATION = true;
private final boolean allowRuntimeCacheCreation; // ๊ธฐ๋ณธ๊ฐ์ true ์
๋๋ค.
private final RedisCacheConfiguration defaultCacheConfiguration;
private final RedisCacheWriter cacheWriter;
private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
1. RedisCacheConfiguration defaultCacheConfiguration
- ๊ธฐ๋ณธ ๊ฐ
- TTL(๋ง๋ฃ ์๊ฐ) ์์
- ์บ์ ๊ฐ์ null ํ์ฉ
- key์ prefix๋ ํ์ฉ(default prefix ๋ Cache Key์ "::" ๋ฅผ ๋ถ์ ๋๋ค.)
- key ์ง๋ ฌํ์ ์ฌ์ฉํ๋ ๊ฐ์ฒด : StringRedisSerializer
- value ์ง๋ ฌํ์ ์ฌ์ฉํ๋ ๊ฐ์ฒด : JdkSerializationRedisSerializer
- DefaultFormattingConversionService ์ JSR-354 Money & Currency and JSR-310 Date-Time ์ ๊ด๋ จ๋๋, ๋ ์ง/์๊ฐ/ํํ์ ๊ด๋ จ์์ ๊ฒ์ ๋๋ค.

2. RedisCacheWriter
RedisCacheWriter๋ Redis์ ๋ํ ์ ์์ค ๋ช ๋ น์ด ์คํ์ ๋ด๋นํ๋ ๊ฐ์ฒด์ ๋๋ค.
RedisCacheWriter provides low-level access to Redis commands (SET, SETNX, GET, EXPIRE,...) used for caching.
class DefaultRedisCacheWriter implements RedisCacheWriter {
private static final boolean REACTIVE_REDIS_CONNECTION_FACTORY_PRESENT = ClassUtils
.isPresent("org.springframework.data.redis.connection.ReactiveRedisConnectionFactory", null);
private final BatchStrategy batchStrategy;
private final CacheStatisticsCollector statistics;
private final Duration sleepTime;
private final RedisConnectionFactory connectionFactory;
private final TtlFunction lockTtl;
private final AsyncCacheWriter asyncCacheWriter;
์ญ์ Redis์ ๋ํ ๋ช ๋ น์ด๋ฅผ ์ ์กํ๊ธฐ ์ํด RedisConnectionFactory ๋ฅผ ํ๋๋ก ๊ฐ๊ณ ์์ต๋๋ค. RedisConnectionFactory ์ ๋ํ ์ค๋ช ์ 'RedisTemplate ๋ด๋ถ ํ๊ตฌ: Spring Data Redis๋ Lettuce๋ฅผ ์ด๋ป๊ฒ ์ถ์ํํ๋๊ฐ?' ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
๊ทธ ์ธ์๋, ์บ์ ํํธ/๋ฏธ์ค ํต๊ณ ์์ง ๋ด๋น์ธ CacheStatisticsCollector๋ ์์ต๋๋ค.
๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ์๋ฉด RedisCacheManager ๊ฐ TTL๋ง๋ฃ, ์ง๋ ฌํ ๋ฐฉ์ ๋ฑ์ ์ง์ ํ๋ ์ญํ , RedisWriter ๋ ๋ ๋์ค ๋ช ๋ น์ด ์คํํ๋ ์ญํ , RedisConnectionFactory ๋ ์ด๋ฌํ ๋ช ๋ น์ด๋ค์ Redis ์ ๋ณด๋ด๋ ์ญํ ์ด ๋ฉ๋๋ค.
@Cacheable ๋์ ๊ณผ์
@EnableCaching ์ ์บ์ฑ์ ํ์ํ Spring Component ๋ค์ ๋ฑ๋กํ๋ ์ฑ ์์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด, @Cacheable์ด ์ ์ธ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ ๋, ์บ์ฑ ๋ก์ง์ ๊ฐ๋ก์ฑ์(intercept) ์คํํ๋ CacheInterceptor๋ฅผ ํ๋ก์(proxy)๋ก ๋ฑ๋กํฉ๋๋ค.
In both of the scenarios above, @EnableCaching and <cache:annotation-driven/> are responsible for registering the necessary Spring components that power annotation-driven cache management, such as the CacheInterceptor and the proxy- or AspectJ-based advice that weaves the interceptor into the call stack when @Cacheable methods are invoked.
- @EnableCaching Javadoc ์ผ๋ถ
@Cacheable ํธ์ถ ์, ํธ์ถ ์์
1. @Cacheable ์ ํธ์ถํ๋ฉด CacheInterceptor ๊ฐ ํธ์ถ๋ฉ๋๋ค.
2. CacheInterceptor ๋ CacheAspectSupport ๋ฅผ ํธ์ถํ๊ณ , CacheAspectSupport ๋ findCachedValue() โ findInCaches() ํตํด Cache ๋ฅผ ์กฐํํฉ๋๋ค.(์ฌ๊ธฐ์๋ RedisCache์ ๋๋ค.)
3. ์กฐํํ Cache ๋ก findInCaches() ์์ doGet( ) ์ ํตํด ์บ์์ Key๋ก Value๋ฅผ ๊ฐ์ ธ์ค๋ ค ํฉ๋๋ค.

4. doGet( ) ์ ๋ฐ๋ผ๊ฐ๋ณด๋ฉด RedisCache ๊ฐ get( ) ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค.
5. get( ) ์ RedisWriter โ RedisConnectionFactory ๋ฅผ ๊ฑฐ์ณ Redis์์ Key์ ๋ํ Value์ ์กฐํํฉ๋๋ค.
6. ๋ง์ฝ Value ๊ฐ ์๋ค๋ฉด getSynchronized() ์ ReentrantLock ์ ์ฌ์ฉํด ๋๊ธฐํ ์์ ์ ์ํํ๊ณ , loadCacheValue()๋ฅผ ํธ์ถํ์ฌ ์ Value๋ฅผ Redis ์ ์ ์ฅํฉ๋๋ค.
7. loadCacheValue ๊ณผ์ ์์ put( ) ์ด ์คํ๋๋ฉฐ, ์ต์ข ์ ์ผ๋ก Redis์ ์๋ก์ด Key-Value ๊ฐ ์ ์ฅ๋ฉ๋๋ค.

๊ฒฐ๋ก
- @Cacheable ํธ์ถ ์, ํด๋น ํค(Key)์ ๋ํ ๊ฐ(Value)์ด ์บ์์ ์กด์ฌํ์ง ์์ผ๋ฉด Redis์ ์๋ก์ด ๊ฐ์ด ์ ์ฅ๋ฉ๋๋ค. ์ด ๊ณผ์ ์ Thread-safeํ๊ฒ ์ด๋ฃจ์ด์ง๋๋ค.
- ์ฆ, @Cacheable ์ ๋์์ 3๋ฒ ํธ์ถํด๋ ๋ด๋ถ์ ์ผ๋ก ๋๊ธฐํ ๋์ด ์ ์์ ์ผ๋ก 3๋ฒ๋ค ํธ์ถ๋๋ค.(ํธ์ถ ์ ๋งํผ counting ๋๋ค๊ณ ์ดํดํ์๋ฉด ์ฌ์ธ ๊ฒ ๊ฐ์ต๋๋ค.)
- @Cacheable(sync=true) ์ต์ ์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์ผํ ํค์ ๋ํด ๊ฐ์ ์์ฒญํ๋๋ผ๋ ์ค์ง ํ๋์ ์ค๋ ๋๋ง ๋ฉ์๋๋ฅผ ์คํํ๋ฉฐ, ๋๋จธ์ง๋ ํด๋น ์์ ์ด ์๋ฃ๋ ๋๊น์ง ๋๊ธฐํ๋ค. ๋ค๋ง, ์ฐ๋ ๋๊ฐ ๋๊ธฐ(Blocking) ๋๋ ์บ์ ๋ฏธ์ค๊ฐ ์์ฃผ ์ผ์ด๋์ง ์์์ผ ํ ๊ฒ์ ๋๋ค.
@CachePut / @CacheEvict์ @Transactional์ ๋์ ๊ด๊ณ
Spring์์ @CachePut๊ณผ @CacheEvict๋ฅผ @Transactional๊ณผ ํจ๊ป ์ฌ์ฉํ ๊ฒฝ์ฐ, ํธ๋์ญ์ ๊ณผ ํจ๊ป ์ํ๋ ์ง ์ฌ๋ถ๋ TransactionAwareCacheDecorator ํด๋์ค์์ ํ์ธํ ์ ์์ต๋๋ค.
Cache decorator which synchronizes its put, evict and clear operations with Spring-managed transactions (through Spring's TransactionSynchronizationManager), performing the actual cache put/evict/clear operation only in the after-commit phase of a successful transaction. If no transaction is active, put, evict and clear operations will be performed immediately, as usual
- TransactionAwareCacheDecorator ์ Javadoc
ํด๋น Javadoc์ ํ์ธํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋์ ๋ฐฉ์์ด ์ ์๋์ด ์์ต๋๋ค.
ํธ๋์ญ์ ์ด ํ์ฑํ๋ ๊ฒฝ์ฐ
- put(์ ์ฅ), evict(์ ๊ฑฐ), clear(์ ์ฒด ์ญ์ ) ์ฐ์ฐ์ด ์ฆ์ ์คํ๋์ง ์๊ณ ๋๊ธฐํ๋ค.
- ํธ๋์ญ์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์ปค๋ฐ(commit)๋ ํ, after-commit ๋จ๊ณ์์ ์ฐ์ฐ์ด ์ํ๋๋ค.
- ์ฆ, ํธ๋์ญ์ ์ด ๋กค๋ฐฑ(rollback)๋๋ฉด ์บ์ ์์ ๋ ์คํ๋์ง ์๋๋ค.
ํธ๋์ญ์ ์ด ์๋ ๊ฒฝ์ฐ
- put, evict, clear ์ฐ์ฐ์ด ์ฆ์ ์คํ๋๋ค.
- ์ผ๋ฐ์ ์ธ ์บ์ ๋์๊ณผ ๋์ผํจ.
Javadoc ์์ ์ค๋ช ํ๋ ์ฝ๋๋ก TransactionAwareCacheDecorator ์ put ๋ฉ์๋๋ฅผ ๊ฐ์ ธ์์ต๋๋ค. this.target ์ Cache ์ธํฐํ์ด์ค๋ฅผ ๊ฐ๋ฆฌํค๋ฉฐ, afterCommit ์์ ํ์ธ ํ ์ ์์ต๋๋ค.

์ ๊ธฐ๋ฅ์ ํ์ฑํ ํ๋ ค๋ฉด CacheManager ๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํ ๋, transactionAware( ) ์ต์ ์ ์ถ๊ฐํฉ๋๋ค.
/**
* Enable {@link RedisCache}s to synchronize cache put/evict operations with ongoing Spring-managed transactions.
* @return this {@link RedisCacheManagerBuilder}.
*/
public RedisCacheManagerBuilder transactionAware() {
this.enableTransactions = true;
return this;
}
๋ง์น๋ฉฐ
์ด๋ฒ ๊ธ์์๋ Redis์ ์ง๋ ฌํ ๋ฐฉ์, ๋น๋๊ธฐ ์์ฒญ ์ฒ๋ฆฌ, Redis ์ํคํ ์ฒ์ ๊ฐ์ ๋ถ๋ถ์ ๋ค๋ฃจ์ง ์๊ณ , Spring์ ์บ์ ์ถ์ํ์ ์ด์ ์ ๋ง์ถฐ ์ดํด๋ณด์์ต๋๋ค.
๊ฐ๋์ ์ด๋ ๊ฒ๊น์ง ๋ด๋ถ ๊ตฌํ์ ๊น์ด ํ๊ณ ๋ค ํ์๊ฐ ์์๊น? ํ๋ ์๊ฐ์ด ๋ค๊ธฐ๋ ํ์ง๋ง, ์ง์ ํ๋ํ๋ ๋ฐ๋ผ๊ฐ๋ค ๋ณด๋ฉด ํฅ๋ฏธ๋กญ๊ธฐ๋ ํ๊ณ , ๋จ์ํ Javadoc์ ์ฝ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๋ง์ ๊ฒ์ ๋ฐฐ์ธ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฌด์๋ณด๋ค ์๊ฐ์ด ์ถฉ๋ถ ํฉ๋๋ค..ใ
ํนํ ์ด๋ฒ ๊ณผ์ ์์ ReentrantLock ์ด ๋ฑ์ฅํ๋๋ฐ, ๋ง์ฝ ์ด ๊ฐ๋ ์ ๋ชฐ๋๋ค๋ฉด ์ฝ๋๋ฅผ ์ฝ๋ ๋์ค์ ์ดํด๊ฐ ์ด๋ ค์ ํฌ๊ธฐํ์์ง๋ ๋ชจ๋ฆ ๋๋ค. ์ด๋ฅผ ํตํด Java ์ CS ์ง์์ด ์ ์ค์ํ์ง ๋ค์ ํ ๋ฒ ๊ณต๊ฐํ์ต๋๋ค.
์ด๋ ๊ฒ ๊ธฐ์ ์ ๋ด๋ถ ๊ตฌํ์ ๋ถ์ํ ์ ์๋ค๋ฉด ์ฌ์ฉ ์ค ๋ฐ์ํ๋ ๋ฌธ์ ๋ ๋ณด๋ค ๋น ๋ฅด๊ฒ ํด๊ฒฐํ ์ ์์ง ์์๊น? ๋ผ๋ ๊ธฐ๋๊ฐ์ด ์๊น๋๋ค. ๊ทธ๋ฆฌ๊ณ ํ์ ์ธ๊ฐ๊ณผ ์ฑ ์ ํตํด์ ๋ถ์กฑํ ๋ด์ฉ์ ์ต๋ํจ๊ณผ ๋์์ ๋ณด๋ค ๋ ์ดํดํ๊ธฐ์ ์ฝ์ง ์์๊น ์๊ฐํฉ๋๋ค.
์ฐธ๊ณ
์บ์ ์ถ์ํ ๋ฌธ์ - https://docs.spring.io/spring-framework/reference/integration/cache/strategies.html
์บ์ ์ด๋ ธํ ์ด์ ๋ฌธ์ - https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html#cache-annotations-cacheable