Spring ‐ Cache 추상화 - thought-corner/Backend-PlayGround GitHub Wiki
캐시의 사용
- 캐시는 서버 부담을 줄이고 성능을 높이기 위해 사용되는 기술이다.
- 어떤 요청을 처리하는데 계산이 복잡하거나 혹은 DB에서 조회되는데 오랜 시간이 걸리는 경우 결과를 저장해두고 가져옴으로써 빠르게 처리할 수 있다.
- 캐시는 값을 저장해두고 불러오기 때문에 데이터의 변동성이 적은 경우에 용이하다.
- 만약 매번 다른 결과를 돌려줘야 하는 상황에 캐시를 적용하게 되면 캐시 업데이트도 해줘야 하고 잘못하면 데이터의 일관성이 낮아지게 되는 문제가 발생할 수 있다.
Spring Cache 추상화
- 스프링은 AOP 방식으로 편리하게 메서드에 캐시 서비스를 적용할 수 있는 기능을 제공한다.
- 캐시 서비스는 트랜잭션과 마찬가지로 AOP를 이용해 메서드 실행 과정에 적용된다.
- 이를 통해 캐시 관련 로직을 핵심 비즈니스 로직과 분리할 수 있고 스프링 캐시 구현 기술에 종속되지 않도록 추상화된 서비스를 제공해 환경이 바뀌거나 적용할 캐시 기술을 변경하더라도 애플리케이션 코드에 영향을 주지 않는다.
Spring Cache 어노테이션 사용법(@Cacheable, @CachePut, @CacheEvict)
- Spring에서
@Cacheable과 같은 어노테이션을 사용하려면 별도의 선언이 필요하다. - 이 때,
@EnableCaching어노테이션을 추가해야한다.
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 세부 구현 ....
}
}
- 캐시를 저장/조회하기 위한 @Cacheable
- 클래스나 인터페이스에서 캐시를 지정할 순 있지만 상당히 드물고, 보통 메서드 단위로 적용한다.
- 캐시를 적용할 메서드에
@Cacheable어노테이션을 붙여주면 캐시에 데이터가 없을 경우에는 기존 로직(DB 조회나 등등)을 실행한 후에 캐시에 데이터를 추가하고, 만약 캐시에 데이터가 있다면 캐시의 데이터를 반환하게 된다.
@Cacheable(cacheManager = "cacheManager", cacheNames = MARKET_TOP_NET_TRADE_VALUE, keyGenerator = "marketCacheKeyGenerator")
public Aclass getAclass() {
// 세부 구현
}
- Spring Framework의 @Cacheable 어노테이션은 **AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)**를 기반으로 동작하며, 반복되는 무거운 연산이나 DB 조회를 최적화하는 데 사용된다.
1. 기본 동작 흐름(The Logic)
- 캐시 조회 : 메서드를 실행하기 전, 파라미터를 기반으로 생성된 Key를 가지고 지정된 **Cache(저장소)**를 뒤진다.
- 캐시 히트(Hit) : 만약 캐시에 값이 있다면, 메서드를 아예 실행하지 않고 저장된 값을 즉시 반환한다.
- 캐시 미스(Miss) : 캐시에 값이 없다면, 실제 메서드를 실행한다.
- 결과 저장 : 메서드가 반환한 값을 캐시에 저장하고, 호출자에게 결과를 전달한다.
2. 내부 기술적 원리(AOP & Proxy)
- Proxy 생성 : 스프링 빈으로 등록된 객체에 @Cacheable이 있으면, 스프링은 해당 빈을 감싸는 프록시 객체를 만든다.
- 인터셉터 : 사용자가 메서드를 호출하면 프록시가 이를 가로채고 CacheInterceptor에게 처리를 맡긴다.
- 결과 반환 : 인터셉터는 캐시 매니저를 통해 데이터를 확인한 뒤, 필요할 때만 실제 타겟 객체의 메서드를 호출한다.
3. 주요 속성별 상세 동작
- Key 생성(key, keyGenerator)
- 기본적으로 모든 메서드 파라미터를 조합해 키를 만든다.
- key 속성에 **SpEL(Spring Expression Language)**을 사용하여 특정 값만 키로 쓸 수 있다.
- 복잡한 로직이 필요하면 keyGenerator를 커스텀하게 구현할 수 있다.
- 조건부 캐싱(condition, unless)
- condition : 메서드 실행 전에 검사합니다. 참일 때만 캐시를 확인/저장한다.
- unless : 메서드 실행 후에 결과를 보고 결정한다.
- 동기화(sync)
- 기본값은 false
- 만약 여러 스레드가 동시에 같은 키로 접근할 때, 실제 메서드가 딱 한 번만 호출되도록 보장하고 싶다면 sync = true로 설정한다.
- Optional 처리
- 코드 주석에도 명시되어 있듯, Optional 반환 타입은 자동으로 언래핑(Unwrap)되어 처리됩니다. 값이 비어있으면 null로 저장된다.
- 주의해야하는점(Self-Invocation)
@Cacheable은 프록시 기반이기 때문에, 같은 클래스 내부에서 메서드를 호출할 때는 동작하지 않는다.
캐시 제거를 위한 @CacheEvict
- 캐시는 적절한 시점에 제거되어야 하는데, 만약 값이 달라진다면 캐시를 제거해야 할 것이다.
- 그렇지 않으면 잘못된 결과를 반환하는데 캐시를 제거하는 방법은 다음과 같이 2가지가 있다.
1. 일정한 주기로 캐시를 제거
2. 값이 변할 때 캐시를 제거
- 스프링은 캐시 제거에도 AOP 기반으로 메서드에 적용할 수 있는
@CacheEvict어노테이션을 제공하고 있다. @CacheEvict에 캐시 이름을 넣어주면 메서드가 실행될 때 캐시 내용이 제거된다.
캐시 저장만을 위한 @CachePut
- 캐시에 값을 저장하는 용도로만 사용되는
@CachePut도 존재한다. @CachePut은@Cacheable과 유사하게 실행 결과를 캐시에 저장하지만 조회 시에 저장된 캐시 내용을 사용하지 않고 항상 메서드 로직을 실행한다는 점에서 다르다.