개요

스프링 프레임워크의 cache abstraction 문서를 간략하게 읽어본다. 실습코드

 

Cache Abstraction

  • 3.1 spring framework 버전부터 나왔다.
  • Cache Abstraction 을 코드에 영향을 최소한으로 하면서 다양한 캐싱 솔루션을 일관되게 사용할 수 있도록 해준다.
  • Cache Abstraction 은 4.1 버전부터는 JSR-107 annotation 의 지원과 함께 확장되었고 커스텀한 옵션들이 생겨났다.

 

Cache vs Buffer

Cache 와 Buffer 두 용어는 서로 바꿔서 사용하는 경향이 있다. 하지만 둘은 엄연히 다른데, Buffer 는 빠른 엔티티와 느린 엔티티간의 중간 임시 저장소로 사용된다. 한 쪽이 다른 한쪽을 기다려야 하기 때문에, 작은 정크 데이터가 아닌 전체 데이터 블록을 한번에 이동할 수 있도록 허용함으로써 이를 완화한다. 데이터는 Buffer 에서 한번만 쓰고 읽는다. 

 

반면에 Cache 는 정의상 숨겨져 있으며, 어느 쪽도 캐싱이 발생한다는 것을 알지 못한다. 그리고 성능은 향상시키면서 동일한 데이터를 빠른 속도로 여러 번 읽게 할 수 있다.

 

이에 대한 추가적인 내용을 살피기 위해선 해당 링크를 확인한다.

 

cache abstraction 은 자바 메소드에 적용할 수 있는데, 적용함으로써 캐시에서 사용할 수 있는 정보에 근거하여 실행 횟수를 감소시킨다. 즉 대상이 되는 메소드가 호출이 될 때마다 cache abstraction 은 캐싱동작을 적용한다. 이 때 캐싱동작의 적용은 해당 메소드에 들어온 인자 값에 대해 이미 호출되었는지 여부를 확인하는 것이다.

 

만약에 실행이 이전에 되었다면 실제 메소드를 호출하지 않고, cached result 를 반환한다. 그러나 이전에 해당 메소드가 호출이 되지 않았다면, 메소드를 실행시킨다. 그리고 그 결과값은 cached 됨과 동시에 클라이언트에 결과값이 반환된다. 이렇게 비용이 많이 드는 CPU, IO-bound 같은 경우는 한번 실행되고 이후부터는 재사용된다. 캐싱 로직은 에 대한 호출자에 대한 간섭없이 투명하게 적용된다.

 

cache abstraction 은 캐시와 관련된 operation 을 제공한다.

  • 캐시의 내용을 업데이트 하는 기능
  • 단일 혹은 전체 캐시를 삭제하는 기능

이는 캐시가 애플리케이션 단에서 변경될 수 있는 프로그램을 처리하는데 유용하다.

 

스프링 프레임워크에서 제공하는 다른 서비스와 마찬가지로 캐싱 서비스또한 추상화되어 있는데, 실제 캐싱을 저장하기 위해서 스토리지를 사용하여야 한다. cache abstraction 은 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 인터페이스에 의해 구체화되어 있다.

 

스프링은 몇가지 cache abstraction 을 제공한다.

  • java.util.concurrent.ConccurentMap
  • Ehcache2.x
  • Gemfire cache
  • Caffeine
  • JSR-107 

=== ( 중략 ) ===

 

🚀🚀🚀

cache abstraction 을 사용하기 위해선 두가지의 측면을 고려해야 한다.

  1. Caching declartion : 캐시를 수행하여야 하는 메소드와 캐시 정책을 확인
  2. Cache configuration : 데이터가 저장되고 읽히는 백업 캐시

스프링의 caching abstraction 은 자바 애노테이션으로 제공한다.

  • @Cacheable : 캐시를 적용한다.
  • @CacheEvict : 캐시 무효화를 수행한다.
  • @CachePut : 캐시 업데이트를 수행한다.
  • @Caching : 메소드에 적용할 여러 캐시작업들을 그룹화한다.
  • @CacheConfig : 클래스 레벨에서 몇가지 공통된 캐시 관련 설정을 공유한다.

 

=== ( 중략 ) ===

 

@Cacheable annotation

@Cacheable 을 사용하여 메소드에 대한 캐시 사용여부를 구분할 수 있다. 

@Cacheable("account")
public Account getAccountById(final Long id) {
    return new Account(id, "PARK", "Seoul");
}

 

Default Key Generation

캐시는 기본적으로 키/밸류 저장소이기 때문에, 캐시에 접근하기 위해서 개별로 수행된 캐시 메소드들은 적절한 키로 변환되어야 한다. caching abstraction 은 아래의 알고리즘에 기반하여 Key Generator 를 사용한다.

  • 파라미터가 주어지지 않았으면, SimpleKey.EMPTY 를 반환한다.
  • 오직 하나의 파라미터가 주어졌으면, 해당 인스턴스를 반환한다.
  • 두 개 이상의 파라미터가 주어졌으면, 모든 파라미터를 포함하는 SimpleKey 를 반환한다.

 

@CachePut annotation

@CachePut 을 메소드의 실행을 방해하지 않고 캐시를 업데이트를 수행할 수 있다. 메소드는 항상 호출되고, 그 결과는 캐시에 저장된다. @Cacheable 옵션과 동일한 옵션이고 메소드의 흐름의 최적화보다 캐시 채우기에 사용해야한다.

@CachePut("account")
public Account updateAccountById(final Long id) {
    return new Account(id, "New Park", "new Seoul");
}
  • @Cacheable 에는 단순히 조회만을 수행하지만, 캐시를 업데이트 하고자한다면, @CachePut 을 사용하여야 한다.

@Cacheable 과 @CachePut 을 구분해서 사용하여야 한다.

  • 동일한 메소드에서 @Cacheable 과 @CachePut 을 사용하는것을 권장하지 않는다. 두 애노테이션은 서로 다른 방식으로 동작하기 때문이다. 
  • @Cacheable 은 메소드를 수행시키지 않고 캐시를 바로 이용한다.
  • @CachePut 은 캐시 업데이트를 하기위해서 메소드의 수행을 강제한다.

 

@CacheEvict annotation

캐시 저장소의 추가 뿐만 아니라, 삭제 또한 가능하다. 해당 프로세스는 사용하지 않거나 오래된 캐시를 삭제하는데 유용하다. @Cacheable 과는 반대로 @CacheEvict 는 캐시를 제거하는 메소드를 구분한다. 해당 메소드는 캐시에서 데이터를 제거하기위한 트리거 역할을 하는 메소드이다. 

 

@CacheEvict 는 작업에 영향을 받는 하나 이상의 캐시를 지정해야하며, 사용자 지정 캐시 및 키 확인 또는 조건 지정을 허용한다. 그리고 추가적인 매개변수인 allEntries 가 있는데, 해당 매개변수는 캐시를 전체 지울 것인지 여부를 결정한다. (키 기반으로 제거하는 것이 아니다.)

@CacheEvict(cacheNames = "account", allEntries = true)
public String deleteAllAccount() {
    // allEntries 옵션을 주어서 cacheNames 에 대한 전체 캐시를 삭제한다.
    // cacheNames 는 @Cacheable 의 값과 일치하여야 한다.
    return "delete all account";
}

 

allEntries 옵션은 전체 캐시 영역을 비워야하는 경우가 있기 때문에 제공된다. 각각의 항목을 일일히 제거하면 비효율적이고 시간이 많이 소요되기 때문에, 모든 엔트리를 하나의 작업을 통해 지우는 것이다. 

 

추가적으로 beforeInvocation 속성을 사용하여, 캐시가 호출된 후 또는 호출되기 전에 eviction 이 발생해야하는지 여부를 결정할 수 있다. beforeInvocation = true 는 메소드가 호출되기 이전에 항상 cache eviction 할 수 있도록 한다. 해당 경우는 cache eviction 이 메소드 결과에 연결될 필요가 없는 경우에 유용하다.

 

@CacheEvict 는 void 타입과 같이 사용할 수 있다.

 

참고자료

Posted by doubler
,