본문 바로가기

Spring Boot/Cache

Spring Cache 추상화 : Spring Cache 핵심 컴포넌트

스프링 캐시에서 핵쉼적인 컴포넌트

@EnableCaching 애너테이션을 @Configuration 클래스에 반드시 추가해줘야

캐시 기능이 활성화된다

 

 - Spring Cache 핵심 컴포넌트인 CacheManager, Cache, CacheResolver의 역할과 책임 이해

 - 각 구성 요소가 캐시 추상화 계층에서 어떤 역할을 수행하는지 학습

 - Cache 객체가 실제 캐시 엔트리를 어떻게 저장, 조회, 삭제하는가?

 - CacheResolver가 복잡한 환경에서 동적으로 캐시를 결정하는 방법

 


Spring Cache 핵심 컴포넌트

Spring Cache는 다음 세 가지 핵심 컴포넌트를 중심으로 동작한다

 - CacheManager : 캐시 생성 및 관리

 - Cache : 실제 캐시 저장소

 - CacheResolver : 캐시 선택 전략 관리

 

세가지 컴포넌트는 Spring Cache 추상화의 핵심 구조를 구성한다.

각자의 책임을 분리하여 캐시를 유연하고 확장 가능하게끔 만듬

CacheManager(2), Cache(3), CacheResolver(1)

 

@Cacheable 메서드 - > CacheResolver에서 캐시 전략 선택 - > CacheManager에서 생성 및 관리 - > 이를 Cache에서 가져옴

Hit : Cached Result를 응답

Miss : 메서드 실행 후 응답

 


CacheManager

1. 역할

CacheManager는 캐시 저장소(Cache)를 생성하고 관리하는 관리자 역할을 함

Spring에서 캐시를 사용할 때, 개발자가 명시적으로 캐시 객체를 만들지 않아도 CacheManager가 알아서 생성하고 관리한다

 

 - 캐시 등록 및 조회 : 지정된 이름의 캐시를 관리함(userCache, productCache 등)

 - 캐시 구현체 선택 : Caffeine, EnCache, Redis 등 다양한 구현체를 지원함

 - 캐시 일관성 유지 : 여러 캐시 간 데이터 일관성을 관리함

 

 

2. CacheManager 구현체의 종류

Spring에서는 캐시 구현체를 지원하기 때문에, 개발 환경에 따라 적절한 CacheManager를 선택할 수 있음

구현체 설명 특징
ConcurrentMapCacheManager 기본 메모리 기반 캐시 설정 없이 바로 사용 가능(테스트환경에 적합)
EnCacheCacheManager
(인캐시 캐시매니저)
디스크 기반 캐시 대용량 캐시 처리, XML 기반 설정 지원
CaffeineCacheManager Java 8 기반 고성능 캐시 만료정책, 용량제한 등 정교한 설정 가능
RedisCacheManager 분산 캐시 서버간 캐시 공유 및 확장성이 우수함

 

 

3. 예제 : Caffeine 기반 CacheManager 설정

 

implementation 참조 : https://yonghwankim-dev.tistory.com/617

build.gradle 설정

    // 이 안에 caffeineCacheManager가 들어있음
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    //caffeine
    implementation 'com.github.ben-manes.caffeine:caffeine'

 

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {
    public CaffeineCacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("userCache", "productCache");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(1000) // 최대 캐시 수 제한
                .expireAfterWrite(10, TimeUnit.MINUTES)); // TTL 설정, 10분

        return cacheManager;
    }
}

 

이렇게 설정하면,

@Cacheable(value = "userCache")와 같은 애너테이션이 지정된 메서드에서 자동으로 Caffeine 캐시를 활용하게 된다고 한다.

 


Cache

1. 역할

Cache 인터페이스는 캐시 데이터의 CRUD를 담당하는 핵심 컴포넌트

실제로 캐시 데이터를 저장하거나 읽어오는 작업은 Cache 객체 내부에서 이뤄진다.

 

메서드 설명
get(Object key) 캐시에서 값을 조회함
put(Object key, Object value) 캐시에 새로운 값을 저장함
evict(Object key) 특정 키의 캐시를 삭제함
clear() 모든 캐시 데이터를 삭제함

 

 

2. 예제 : Cache 직접 사용

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    private final CacheManager cacheManager;

    public ProductService(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    public String cacheExample() {
        Cache cache = cacheManager.getCache("productCache");
        // 캐시에 데이터를 저장함
        cache.put("item1", "Laptop");

        // 캐시에서 데이터를 조회함
        String product = cache.get("item1", String.class);
        System.out.println("조회 결과 : " + product);

        // 캐시 삭제
        cache.evict("item1");
        return "조회 결과 : " + product;
    }
}

 

* @Cacheable이 없어도 CacheManager를 통해 직접 캐시를 조회할 수 있다.

 

 

3. 캐시 엔트리 관리 다이어그램

CacheManager에서 Cache를 통해 CRUD가 동작한다

 

 


CacheResolver

1. 역할

CacheResolver는 복수의 캐시가 존재하는 환경에서, 어떤 캐시를 사용할지 동적으로 결정하는 역할을 함

기본적으로 Spring은 SimpleCacheResolver를 사용하며, CacheManager가 제공하는 캐시를 그대로 사용함

그러나 특정 조건(사용자의 유형, API 경로, 데이터 크기 등..)에 따라 캐시 선택 로직을 커스터마이징해야 하는 경우, 직접 구현할 수 있다고 한다.

 

 

2. 예제 : Custom CacheResolver 구현

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;

@Component
public class CustomCacheResolver implements CacheResolver {
    private final CacheManager cacheManager;

    public CustomCacheResolver(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }
    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        String methodName = context.getMethod().getName();

        // 메서드명에 따라 다른 캐시를 선택한다
        if(methodName.startsWith("getUser")) { // 메서드명이 getUser로 시작하면
            return List.of(cacheManager.getCache("userCache")); // userCache를 씀
        }
        return List.of(cacheManager.getCache("defaultCache")); // 아니면 defaultCache를 씀
    }
}

 

* getUser 형태의 메서드(getUser로 시작하는 메서드)는 자동으로 userCache를 사용하게 된다.

 

 

3. CacheResolver 동작 다이어그램

CacheResolver 동작 다이어그램

 

@Cacheable 메서드 - > CustomCacheResolver - > 메서드명에 따라 userCache or defaultCache -> CacheManager

 


 

* CacheManager는 하나의 빈(bean)으로 등록되어 전역에서 공유된다

* 서비스별로 캐시 정책이 다를 경우, 여러 CacheManager를 만들어 분리 운영할 수 있음

* CacheResolver를 통해 API별 캐시 구분 로직을 추가하면, 대규모 서비스에서 캐시 오염을 방지할 수 있음

* 캐시 이름은 반드시 명확한 도메인 명명 규칙을 따르는게 좋음(userCache, contentCache, articleCache 등)

 


정리

요소 역할 주요 메서드, 특징
CacheManager 캐시 생성 및 관리 getCache(), 다양한 구현체 지원
Cache 실제 캐시 저장소 get(), put(), evict(), clear()
CacheResolver 캐시 선택 로직 관리 resolveCaches() 메서드로 조건별 캐시 선택