<aside>
앞서 캐시를 서비스에서 어떻게 사용하고 있는지 설명했다
하지만 중요한거는 레디스에 대한 장애 조치가 되어 있지 않다는 점이다.
처음에는 레디스 장애시 기본적으로 데이터베이스를 조회하도록 구현했지만 해당 방식보다 로컬 캐시를 활용하는 것이 성능상 더 좋다고 판단했다
따라서 캐시를 이중화하여 기본적으로 레디스 캐시를 사용하고 만약 장애 발생 시 로컬 캐시를 사용한다
</aside>
<aside>
<aside>
로컬 캐시 라이브러리로 카페인을 선택하였다. 그 이유를 아래에서 확인해보자
</aside>
<aside>
카페인은 W-TinyLFU(Window TinyLfu)라는 고급 캐시 제거 정책을 사용한다. 이는 LRU와 LFU 알고리즘의 장점을 결합하여 기존 LRU보다 높은 적중률을 달성한다.
즉 높은 적중률을 통해 메모리 관리 시 캐시 미스가 발생하는 빈도를 줄여주는 것이다.
</aside>
<aside>
ConcurrentHashMap의 경우 구현이 쉽고 빠르지만, 메모리 관리를 우리가 직접해야한다는 점에서 문제가 있다. 즉, TTL 정책이나 캐시 정책같은 것들에 대한 관리가 쉽지 않다.
</aside>
<aside>
선언적 캐싱 애노테이션 사용할 수 있다. 예를 들어 @Cacheable
, @CachePut
, @CacheEvict
와 같은 스프링의 캐싱 애노테이션은 카페인과 함께 원활하게 사용할 수 있다.
</aside>
<aside>
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(7))
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(cf).cacheDefaults(cacheConfiguration).build();
}
@Bean
public CaffeineCacheManager caffeineCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager(
PAGE_CACHE_KEY, SUB_ITEM_CACHE_KEY
);
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(60, TimeUnit.MINUTES));
return manager;
}
@Bean
@Primary
CacheManager cacheManager(
RedisCacheManager redisCacheManager,
CaffeineCacheManager caffeineCacheManager
) {
return new TwoLevelCacheManager(redisCacheManager, caffeineCacheManager);
}
</aside>