Backend/SpringBoot

Local Cache, Caffein

Dean83 2026. 1. 5. 18:41

https://dean83.tistory.com/323 을 통해 Redis 를 이용한 캐시 사용법 정리 및 기본적인 내용을 정리해 두었다. 

로컬 캐시는 개인적으로는 잘 안쓸거 같아서 정리를 하지 않았는데, 경우에 따라서는 사용하는곳이 꽤 있는거 같다. 

 

로컬캐시는 여러개가 있는데 Caffein이 많이 쓰이기도 하고, 여러 기능이 다양해서 많이 쓰인다고 한다. (모체가 구글에서 제작)

 

build.gradle 설정 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'com.github.ben-manes.caffeine:caffeine'
}

 

application.yaml 설정 추가

spring:
  cache:
    type: caffeine

 

CacheManager 구성

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CaffeineCacheManager cacheManager() {

        CaffeineCacheManager cacheManager =
            new CaffeineCacheManager(
                "userById",
                "bookById",
                "popularBooks"
            );

        cacheManager.setCaffeine(
            Caffeine.newBuilder()
            .initialCapacity(1_000)
            .maximumSize(10_000)
            .expireAfterAccess(10, MINUTES)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .recordStats()
            );
        );
        

        return cacheManager;
    }
}
  • userById .... 인자값 : 미리 생성할 캐시 목록으로, @Cacheable 에서 사용할 이름들 (중요 캐시들을 미리 선언)
  • initialCapacity : 초기 캐시 엔트리 갯수
    • 너무 크게주면 메모리 낭비가 되므로, maximumSize의 10 ~ 20% 사이가 좋다.
  • maximumSize : 최대 저장 가능한 엔트리 갯수로, 이 숫자를 넘기면 자동으로 삭제한다
  • expireAfterWrite :  캐시에 저장한 시간이 이 시간을 넘어가면 삭제
  • expireAfterAccess : 마지막으로 접근한 시간이 이 시간을 넘어가면 삭제
  • refreshAfterWrite : 정해진 시간마다 비동기로 캐시를 자동 갱신 하는 기능이나 잘 쓰지 않음. 
    • Redis를 사용하는쪽이 더 좋고, 비동기 제어 및 트랜잭션 관리가 어려움.
  • recordStats() : 캐시 통계를 위해 반드시 호출해야 함

캐시 통계 내용 확인

구성하기 나름이나,  히트율이 낮을경우 알림 같은걸 추가 할 수도 있다.

예시로, 이대로 쓰면 로그가 엄청 많이 생성된다.

@Component
@RequiredArgsConstructor
@Slf4j
public class CacheMetricsScheduler {

    private final CacheManager cacheManager;

    /**
     * 1분마다 모든 캐시 성능 출력
     */
    @Scheduled(fixedRate = 60_000)
    public void printAllCacheStats() {

        log.info("========== Cache Metrics ==========");

        for (String cacheName : cacheManager.getCacheNames()) {

            Cache cache = cacheManager.getCache(cacheName);
            if (cache == null) {
                continue;
            }

            if (!(cache instanceof CaffeineCache)) {
                continue;
            }

            CaffeineCache caffeineCache = (CaffeineCache) cache;
            com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache =
                caffeineCache.getNativeCache();

            CacheStats stats = nativeCache.stats();

            log.info(
                "[Cache: {}] size={}, hitRate={}, hitCount={}, missCount={}, evictionCount={}",
                cacheName,
                nativeCache.estimatedSize(),
                String.format("%.2f", stats.hitRate()),
                stats.hitCount(),
                stats.missCount(),
                stats.evictionCount()
            );
            
            if (stats.hitRate() < 0.6) {
    			log.warn("[Cache WARNING] {} hitRate={}", cacheName, stats.hitRate());
}
        }

        log.info("===================================");
    }
}