Spring Cache 개요
Spring Cache는 Spring 3.1부터 도입된 캐싱 추상화 프레임워크로, AOP를 활용해 어노테이션 기반 캐싱을 제공합니다. 주요 특징:
- 코드 변경 최소화:
@Cacheable등 간단한 어노테이션으로 캐싱 구현 - 다양한 캐시 구현체 지원: CacheManager 인터페이스를 통해 Caffeine, Ehcache 등과 통합
- JSR-107 표준 호환: Spring 4.1 이상에서 JCache 어노테이션 지원
Spring Cache 핵심 구성요소
인터페이스
- Cache: RedisCache, ConcurrentMapCache 등 실제 캐시 연산 정의
- CacheManager: RedisCacheManager 같은 캐시 컴포넌트 관리자
- CacheResolver: 캐시 해석기 지정
주요 어노테이션
@Cacheable: 메서드 실행 전 캐시 확인@CacheEvict: 캐시 데이터 삭제@CachePut: 반환값 캐시 저장@Caching: 다중 캐시 어노테이션 조합
Redis 통합 구현
@Configuration
@EnableCaching
public class RedisIntegrationConfig {
@Bean
public CacheManager cacheManager() {
return new CustomCacheManager();
}
}
커스텀 CacheManager
public class CustomCacheManager implements CacheManager {
private final Map<String, Cache> caches = new ConcurrentHashMap<>();
@Override
public Cache getCache(String identifier) {
String[] segments = identifier.split("#");
String baseName = segments[0];
Cache existing = caches.get(baseName);
if(existing != null) return existing;
CacheConfig settings = new CacheConfig();
if(segments.length > 1)
settings.setExpiration(Duration.parse(segments[1]).toMillis());
Cache newCache = createCacheInstance(baseName, settings);
caches.put(baseName, newCache);
return newCache;
}
private Cache createCacheInstance(String name, CacheConfig config) {
// Redis 연결 및 캐시 생성 로직
}
}
캐시 어노테이션 활용 예시
@Cacheable(value = "userData#60s", key = "#userId")
public User fetchUser(String userId) {
return userRepository.findById(userId);
}
@CacheEvict(value = "userData", allEntries = true)
public void clearUserCache() {}
캐시 유틸리티 클래스
public final class CacheHelper {
private static final CacheManager MANAGER = ApplicationContext.getBean(CacheManager.class);
public static Object retrieve(String cacheName, Object key) {
return MANAGER.getCache(cacheName).get(key).get();
}
public static void store(String cacheName, Object key, Object value) {
MANAGER.getCache(cacheName).put(key, value);
}
}
캐시 문제 해결 전략
캐시 Avalanche (Cache Avalanche)
원인: 다수 키의 동시 만료로 인한 DB 부하
해결책:
- TTL 분산 설정 (예:
cache#60s#10m) - Redis 클러스터 구성
- 핫 데이터 영구 캐싱
캐시 Breakdown (Cache Breakdown)
원인: 핫 키 만료 시 동시 요청 급증
해결책:
@Cacheable(sync = true)를 통한 동기화 처리
@Cacheable(value = "hotData", key = "#id", sync = true)
public Data fetchHotData(String id) { ... }
캐시 Penetration (Cache Penetration)
원인: 존재하지 않는 데이터에 대한 지속적 요청
해결책:
- Bloom Filter를 통한 사전 차단
- Null 값 캐싱 (예:
@Cacheable(unless = "#result == null"))