Spring Cache 기본

Spring Cache


Cache abstraction에 대한 이해

기본적으로 캐싱반복적으로 접근하는 데이터에 대한 성능 향상을 위해 사용된다.

cache abstraction자바 method에 캐싱을 적용하여 데이터 소스에 대한 access 횟수를 줄인다.

이 말의 의미는, 캐싱된 메서드가 호출될 때 마다 cache abstraction은 이전에 동일한 인자와 함께 메서드가 호출되었는지 여부를 확인한다.

  • 만일 이미 동일 인자에 대한 메서드 호출이 캐싱되었다면, **실제 메서드 호출은 하지 않고! 이미 캐싱된 결과를 반환한다.**
  • 만일 동일 인자에 대한 메서드 호출이 캐싱되어있지 않다면, **실제 메서드 호출 후 결과를 캐싱하고 이후 동일 인자에 대해 실제 메서드 호출은 하지 않고 캐싱 결과를 반환한다.**

위와 같은 과정을 통해 실제 메서드 호출 로직이 비용이 비싼 경우 이 비용을 절감할 수 있다.

중요 : 위와 같은 메서드 + 인자 캐싱동일 인자에 대해 동일한 결과를 보장하는 경우에 유효하다.

이것 말고도 캐시와 관련된 여러 연산들(캐싱된 요소를 업데이트, 캐싱 요소들 삭제)에 대해서 Cache abstraction이 제공을 해준다.

만일 어플리케이션 런타임에 데이터가 바뀔 가능성이 있는 경우 위와 같은 연산들을 유용하게 쓸 수 있다.

Spring Framework의 다른 서비스들처럼, Caching service는 일종의 추상화이다.

따라서 cache-data를 저장할 스토리지가 필요하다.

다시말해, 캐시 추상화은 개발자를 캐시 로직 구현으로부터 자유롭게 해주지만, 캐시 데이터 저장소를 제공해주지는 않는다.

캐시 추상화org.springframework.cache.Cacheorg.springframework.cache.CacheManager 인터페이스들로 구현 가능하다.

기본적으로 ConcurrentMap, Ehcache, Caffeine 등과 같이 추상화를 구현하는 구현체들이 존재한다.

중요: Caching abstraction은 멀티 프로세스, 멀티 스레드를 다루기 위한 별도의 환경을 제공하지 않기 때문에 구현체에서 해결해야 한다.


유의

캐시 추상화을 사용하기 위해서 개발자는 다음의 두 가지를 유의해야 한다.

  • Caching declaration : 캐싱을 하기 위한 메서드를 식별
  • Cache configuration : 캐시를 읽고 저장데이터 스토리지 마련

@EnableCaching

스프링의 어노테이션 기반 cache management 기능을 활성화 시킨다.

@EnableCaching@Configuration은 아래와 같이 함께 사용 가능하다.

@EnableCachingCacheInterceptor, Proxy-, AspectJ-based와 같이 @Cacheable 메서드가 호출될 때 인터셉터를 call-stack에 삽입하는 어노테이션 기반 cache 지원 컴포넌트들을 구성하고 빈으로 등록할 책임이 있다.

  • CacheInterceptor, Proxy-, Aspect-J와 같은 녀석들은 Cacheable 메서드가 호출되면 바이트코드를 조작하는 등의 기법을 통해 인터셉터를 call-stack에 삽입함으로써 추가 로직이 실행되도록 한다.
  • 그리고 위와 같은 녀석들이 어노테이션 기반의 cache 컴포넌트인데, 이런 녀석들을 스프링 컴포넌트로 구성하고 빈으로 등록하는 책임을 @EnableCaching이 담당한다.

인용 : The @EnableCaching annotation triggers a post-processor that inspects every Spring bean for the presence of caching annotations on public methods. If such an annotation is found, a proxy is automatically created to intercept the method call and handle the caching behavior accordingly.


어노테이션 기반의 Cache Declaration

캐시 선언을 위해서 abstraction은 몇 가지의 자바 어노테이션을 제공한다.

  • @Cacheable : 캐싱 채우기?
    • 캐싱 대상이 되는 메서드를 지정한다. 그러면 해당 메서드 실행 시 결과를 캐시에 저장하고 이후 **동일한 arguments**로 재호출 시 실제 메서드를 실행하지 않고 캐싱된 결과가 반환된다.
    • 이 어노테이션에는 캐싱 데이터 구분을 위한 이름이 필요하다.

findBook 메서드는 books라는 캐시 이름과 연동된다. 메서드 호출 시 마다 캐시는 이미 호출이 되어서 반복 호출할 필요가 없는지를 확인한다.

대부분의 경우에 하나의 캐시(books)만 선언되는데, 가끔 가다 여러개의 캐시가 필요할 때가 있다.

그런 경우 findBook 호출 시 마다 해당 캐시들(books, isbns)을 모두 확인하는 과정을 거친다. 이 중 만일 하나라도 캐싱된 데이터가 존재한다면(hit) 해당 데이터가 반환된다.


Default Key 생성

cache는 기본적으로 key-value 저장소이기 때문에, 캐싱된 메서드 호출 시 **캐시에 접근하기 위한 key**가 필요하다.

caching abstraction은 기본적으로 간단한 KeyGenerator 기반의 알고리즘을 사용한다.

  • 파라미터가 없는 경우, SimpleKey.EMPTY를 사용
  • 파라미터가 한 개 존재한는 경우, 해당 파라미터를 Key로 사용
  • 파라미터가 한 개 이상 존재하는 경우, 모든 파라미터가 포함된 SimpleKey를 사용

만일 각 파라미터들이 hashCode(), eqauls()를 구현한다면 이상 없이 잘 동작하지만, 그렇지 않은 경우 별도의 알고리즘을 사용해야한다.

별도의 알고리즘 구현을 위해 org.springframework.cache.interceptor.KeyGenerator interface를 제공한다.


Caching key 지정

메서드의 인자로 간단히 캐싱하기가 애매한 경우가 존재한다.

이를테면 메서드에 여러 인자가 존재하지만 이 중 일부 인자만 캐싱에 사용되는 경우를 들 수 있다.

위 메서드 인자를 보면, 두 개의 boolean 변수가 존재하지만 캐싱에 사용되지는 않는다. (equals(), hashCode()를 구현하지 않는 인자이기 때문에)

만일 둘 중 하나는 중요한 변수고 다른 하나는 중요하지 않은변수라면 어떻게 캐싱을 해야할까?

이를 위해 @Cacheable 어노테이션은 key 속성을 이용해서 key 생성을 위한 방법을 지정할 수 있다.

SpEL을 사용해서 캐싱을 위해 사용될 파라미터를 선택하거나 어떤 연산을 사용하거나, 임의의 메서드 실행 결과를 key의 구성으로 사용할 수 있다.


캐싱 조건 설정

캐시 어노테이션은 SpEL 기반의 파라미터를 이용하여 조건을 설정할 수 있다. 만일 설정된 조건이 true캐싱이 되고 false면 캐싱되지 않는다.

또한 unless 키워드를 활용해서 조건을 추가할 수 있다. 단, unless메서드 실행 이후에 조건 검사를 실행하여 캐싱 여부를 결정한다.

위 예제에서 인자로 전달된 name의 길이가 32 미만인 경우, 결과 Book 인스턴스hardback의 상태가 true라면 캐싱을 하고 아니라면 캐싱을 하지 않는다.


@CachePut 어노테이션

어떤 메서드 실행 시 cache 업데이트가 필요할 때 @CachePut 어노테이션을 사용할 수 있다.

@CachePut 어노테이션이 선언된 메서드는 항상 실행되고, 그 결과는 캐시에 반영이 된다.


@CacheEvict 어노테이션

cache abstraction캐시 저장뿐 아니라 캐시 제거도 지원한다. 이 기능은 캐시 내 오래되어 필요 없어진 데이터 제거에 유용하다.

allEntries 키워드는 캐시 내 모든 데이터 제거에 유용하다.


@CacheConfig 어노테이션

메서드 단위가 아닌 클래스 단위로 캐싱 설정을 하고 싶다면 @CacheConfig가 유용하다.

이 어노테이션이 선언된 클래스의 내부 요소들은 **캐시 설정(캐시 이름 등)**을 공유한다.


참고