토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

스프링 부트 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

목차


7.13. Caching

스프링 프레임워크를 사용하면 코드 몇 줄만으로 애플리케이션에 캐시를 추가할 수 있다. 본질적으로 스프링 프레임워크가 제공하는 캐시는 메소드에 캐시를 적용하므로, 캐시에 있는 정보를 기반으로 메소드의 실행 횟수는 줄어들게 된다. 캐시 로직은 호출자를 간섭하는 일 없이 그저 투명하게 적용된다. 스프링 부트는 @EnableCaching 어노테이션을 통해 캐시를 활성화해주면 캐시 인프라를 자동으로 설정해준다.

자세한 내용은 스프링 프레임워크 레퍼런스에서 관련 섹션을 확인해봐라.

한 줄로 요약하자면, 서비스를 실행할 때 캐시 처리를 추가하고 싶으면 다음 예제처럼 서비스의 메소드에 관련 어노테이션을 추가하면 된다:

@Component
public class MyMathService {

    @Cacheable("piDecimals")
    public int computePiDecimal(int precision) {
        ...
    }

}

이 예제에선 잠재적인 비용이 큰 작업에 캐시를 사용하는 모습을 시연하고 있다. 스프링 프레임워크의 추상화 덕분에, computePiDecimal을 호출하기 전에 piDecimals 캐시에서 i 인자와 일치하는 항목을 찾게 된다. 일치하는 항목을 발견하면 캐시에 있던 컨텐츠를 곧바로 호출자에게 반환하며, 메소드는 호출되지 않는다. 일치하는 항목이 없으면 메소드를 호출하고, 값을 반환하기 전에 캐시를 업데이트한다.

표준 JSR-107 (JCache) 어노테이션(ex. @CacheResult)도 투명하게 사용할 수 있다. 단, Spring Cache와 JCache 어노테이션을 섞어 쓰지 않기를 강력히 권하는 바다.

특정한 캐시 라이브러리를 추가하지 않았다면 스프링 부트는 메모리에서 concurrent map을 사용하는 simple provider를 자동 설정한다. 캐시가 필요할 때는 (위 예제의 piDecimals처럼) 이 provider가 캐시를 생성해줄 거다. simple provider는 프로덕션 용도로는 권장하지 않지만, 처음 시작하면서 기능을 익힐 때는 정말 좋다. 사용할 캐시 provider를 결정했으면, 꼭 그 provider의 문서를 읽고 애플리케이션에 캐시를 구성하는 방법을 찾아봐라. 거의 모든 provider에선 애플리케이션에서 사용하는 캐시를 전부 직접 명시해야 한다. spring.cache.cache-names 프로퍼티로 정의한 디폴트 캐시를 커스텀할 수 있는 방법을 제공하는 provider도 있다.

캐시를 업데이트하거나 evict시키는 것도 투명하게 가능하다.

7.13.1. Supported Cache Providers

캐시 추상화는 실제 저장소를 제공하지 않으며, org.springframework.cache.Cacheorg.springframework.cache.CacheManager 인터페이스로 실체화되는 추상화가 저장소를 결정한다.

CacheManager 타입 빈이나 CacheResolver 빈을 cacheResolver란 이름으로 정의하지 않았다면 (CachingConfigurer 참고) 스프링 부트는 아래 provider를 찾아본다 (나타낸 순서대로):

  1. Generic
  2. JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan 등)
  3. EhCache 2.x
  4. Hazelcast
  5. Infinispan
  6. Couchbase
  7. Redis
  8. Caffeine
  9. Simple

spring.cache.type 프로퍼티를 설정하면 특정 캐시 provider를 강제하는 것도 가능하다. 특정 환경에서 (테스트환경 등) 캐시를 전부 비활성화해야 할 때는 이 프로퍼티를 사용해라.

spring-boot-starter-cache “스타터”를 사용하면 기본적인 캐시 의존성을 간단하게 추가할 수 있다. 이 스타터는 spring-context-support를 가져온다. 의존성을 수동으로 추가할 때는 JCache나, EhCache 2.x, Caffeine 지원을 이용하려면 반드시 spring-context-support를 넣어줘야 한다.

CacheManager를 스프링 부트로 자동 설정할 때는, CacheManagerCustomizer 인터페이스를 구현하는 빈을 정의해주면 CacheManager가 완전히 초기화되기 전에 설정을 추가로 조정할 수 있다. 아래 예제에선 내부 맵으로 null 값을 전달해야 한다는 플래그를 설정하고 있다:

@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {

    @Bean
    public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
        return (cacheManager) -> cacheManager.setAllowNullValues(false);
    }

}

위 예제에선 ConcurrentMapCacheManager가 자동 설정된다고 가정한다. 그렇지 않으면 (자체 설정을 제공했거나, 다른 캐시 provider를 자동 설정했다면) customizer는 전혀 호출되지 않는다. customizer는 원하는 만큼 만들 수 있으며, @OrderOrdered를 사용해 순서를 지정해도 된다.

Generic

컨텍스트가 org.springframework.cache.Cache 빈을 최소 하나라도 정의한다면 Generic 캐시를 사용한다. Cache 타입 빈을 전부 래핑하는 CacheManager를 생성한다.

JCache (JSR-107)

JCache는 클래스패스에 javax.cache.spi.CachingProvider가 있을 때 부트스트랩되며 (즉, 클래스패스에 JSR-107과 호환되는 캐시 라이브러리가 있을 때), JCacheCacheManagerspring -boot-starter-cache “스타터”가 제공한다. 다양한 호환 라이브러리를 사용할 수 있으며, 스프링 부트는 Ehcache 3, Hazelcast, Infinispan을 위한 의존성 관리를 제공한다. 필요하면 다른 호환 라이브러리도 추가할 수도 있다.

둘 이상의 provider가 있을 수도 있는데, 이럴 땐 provider를 명시적으로 지정해줘야 한다. JSR-107 표준에선 설정 파일 위치를 정의하는 표준화된 방법은 없지만, 스프링 부트에선 구현체에 따른 캐시 설정도 최대한 수용한다:

properties yaml
# Only necessary if more than one provider is present
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml
# Only necessary if more than one provider is present
spring:
  cache:
    jcache:
      provider: "com.example.MyCachingProvider"
      config: "classpath:example.xml"

스프링 부트는 캐시 라이브러리가 네이티브 구현과 JSR-107 지원을 모두 제공할 때는 JSR-107 지원을 선호하므로, 다른 JSR-107 구현체로 전환해도 동일한 기능을 사용할 수 있다.

스프링 부트는 Hazelcast를 범용적으로 지원하고 있다. 단일 HazelcastInstance를 사용할 수 있다면 spring.cache.jcache.config 프로퍼티를 지정하지만 않으면 자동으로 CacheManager에서도 재사용한다.

내부 javax.cache.cacheManager를 커스텀하는 방법은 두 가지가 있다:

표준 javax.cache.CacheManager 빈을 정의하면, 추상화에서 사용하는 org.springframework.cache.CacheManager 구현체로 자동으로 래핑된다. 여기에는 더 이상의 커스텀은 적용되지 않는다.

EhCache 2.x

EhCache 2.x는 클래스패스 루트에 ehcache.xml이란 파일이 있을 때 사용한다. EhCache 2.x를 발견하면 spring-boot-starter-cache “스타터”에서 제공하는 EhCacheCacheManager를 통해 캐시 매니저를 부트스트랩한다. 다음 예제처럼 다른 설정 파일을 제공할 수도 있다:

properties yaml
spring.cache.ehcache.config=classpath:config/another-config.xml
spring:
  cache:
    ehcache:
      config: "classpath:config/another-config.xml"

Hazelcast

스프링 부트는 Hazelcast를 범용적으로 지원하고 있다. HazelcastInstance 하나가 자동 설정되면, 자동으로 CacheManager로 래핑한다.

Infinispan

Infinispan에선 디폴트 설정 파일 위치가 없기 때문에 직접 명시해줘야 한다. 명시하지 않으면 디폴트 부트스트랩을 사용한다.

properties yaml
spring.cache.infinispan.config=infinispan.xml
spring:
  cache:
    infinispan:
      config: "infinispan.xml"

spring.cache.cache-names 프로퍼티를 사용하면 기동 시에 캐시를 생성할 수 있다. 커스텀 ConfigurationBuilder 빈을 정의하면 이 빈을 캐시 커스텀에 사용한다.

스프링 부트에선 Infinispan을 임베디드 모드 정도만 매우 기본적인 수준으로 지원한다. 더 많은 옵션을 사용하고 싶다면 공식 Infinispan 스프링 부트 스타터를 사용해야 한다. 자세한 내용은 Infinispan의 문서를 참고해라.

Couchbase

Spring Data Couchbase 의존성이 있고 Couchbase를 설정했다면 CouchbaseCacheManager를 자동 설정한다. spring.cache.cache-names 프로퍼티로 기동 시 추가적인 캐시를 생성할 수 있으며, spring.cache.couchbase.* 프로퍼티로 캐시 기본값을 설정할 수 있다. 예를 들어, 아래 설정에선 10분뒤 만료되는 cache1, cache2 캐시를 생성한다:

properties yaml
spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m
spring:
  cache:
    cache-names: "cache1,cache2"
    couchbase:
      expiration: "10m"

직접 만져야 하는 설정이 더 많다면 CouchbaseCacheManagerBuilderCustomizer 빈을 등록하는 것도 좋다. 다음은 cache1과, cache2에 만료 기간을 따로 지정하는 customizer 예시다:

@Configuration(proxyBeanMethods = false)
public class MyCouchbaseCacheManagerConfiguration {

    @Bean
    public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache1", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofSeconds(10)))
                .withCacheConfiguration("cache2", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofMinutes(1)));

    }

}

Redis

Redis 의존성이 있고 설정도 해줬다면 RedisCacheManager를 자동으로 설정한다. spring.cache.cache-names 프로퍼티로 기동 시 추가적인 캐시를 생성할 수 있으며, spring.cache.redis.* 프로퍼티로 캐시 기본값을 설정할 수 있다. 예를 들어, 아래 설정에선 ttl이 10분인 cache1, cache2 캐시를 생성한다:

properties yaml
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m
spring:
  cache:
    cache-names: "cache1,cache2"
    redis:
      time-to-live: "10m"

기본적으로 두 캐시가 같은 키를 사용하면, Redis에서 키가 중복돼 잘못된 값을 반환하는 일이 없도록 키 프리픽스를 추가한다. 자체 RedisCacheManager를 생성한다면 웬만하면 이 설정은 활성화된 채로 유지하는 게 좋다.

자체 RedisCacheConfiguration @Bean을 추가하면 디폴트 설정을 전부 직접 제어할 수 있다. 디폴트 직렬화 전략을 커스텀하고 싶을 때 유용할 거다.

직접 만져야 하는 설정이 더 많다면 RedisCacheManagerBuilderCustomizer 빈을 등록하는 것도 좋다. 다음은 cache1과, cache2에 ttl을 따로 지정하는 customizer 예시다:

@Configuration(proxyBeanMethods = false)
public class MyRedisCacheManagerConfiguration {

    @Bean
    public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache1", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofSeconds(10)))
                .withCacheConfiguration("cache2", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofMinutes(1)));

    }

}

Caffeine

Caffeine은 Guava 대신 사용할 수 있는, Guava 캐시를 자바 8로 재작성한 라이브러리다. Caffeine이 있으면 CaffeineCacheManager를 자동 설정한다 (spring-boot-starter-cache “스타터”에서 제공). 캐시는 spring.cache.cache-names 프로퍼티로 기동 시 생성할 수 있으며, 다음 중 하나를 통해 커스텀할 수 있다 (나타낸 순서대로):

  1. spring.cache.caffeine.spec으로 캐시 스펙 정의
  2. com.github.benmanes.caffeine.cache.CaffeineSpec 빈 정의
  3. com.github.benmanes.caffeine.cache.Caffeine 빈 정의

예를 들어 아래 설정에선 최대 크기가 500, ttl이 10분인 cache1, cache2 캐시를 생성한다:

properties yaml
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
spring:
  cache:
    cache-names: "cache1,cache2"
    caffeine:
      spec: "maximumSize=500,expireAfterAccess=600s"

com.github.benmanes.caffeine.cache.CacheLoader 빈을 정의하면 자동으로 CaffeineCacheManager에 연결된다. CacheLoader는 캐시 매니저가 관리하는 모든 캐시와 연결되므로 CacheLoader<Object, Object>로 정의해야 한다. 자동 설정에선 다른 제네릭 타입은 전부 무시한다.

Simple

다른 provider를 찾을 수 없으면 ConcurrentHashMap을 캐시 저장소로 사용하는 simple 구현체를 설정한다. 애플리케이션에 캐시 라이브러리가 없을 때 사용하는 기본값이다. 기본적으로 캐시는 필요에 따라 생성되지만 cache-names 프로퍼티를 설정하면 사용할 수 있는 캐시 리스트를 제한할 수 있다. 예를 들어 cache1cache2 캐시만 사용하고 싶다면 cache-names 프로퍼티를 다음과 같이 설정해라:

properties yaml
spring.cache.cache-names=cache1,cache2
spring:
  cache:
    cache-names: "cache1,cache2"

이렇게 설정했을 때 애플리케이션이 여기 나열하지 않은 캐시를 사용하면, 해당 캐시가 필요한 런타임엔 실패하게 되지만, 기동 시에는 실패하지 않는다. 선언하지 않은 캐시를 사용할 때의 동작은 “실제” 캐시 provider의 동작 방식과 유사하다.

None

설정에 @EnableCaching이 있으면 적절한 캐시 설정도 찾기 마련이다. 특정 환경에선 캐시를 전부 비활성화해야 한다면, 다음 예제처럼 캐시 타입을 none으로 강제해서 no-op 구현체를 사용해라:

properties yaml
spring.cache.type=none
spring:
  cache:
    type: "none"

전체 목차는 여기에 있습니다.

<< >>

TOP