스프링 부트 공식 레퍼런스를 한글로 번역한 문서입니다.
전체 목차는 여기에 있습니다.
목차
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도 있다.
7.13.1. Supported Cache Providers
캐시 추상화는 실제 저장소를 제공하지 않으며, org.springframework.cache.Cache
와 org.springframework.cache.CacheManager
인터페이스로 실체화되는 추상화가 저장소를 결정한다.
CacheManager
타입 빈이나 CacheResolver
빈을 cacheResolver
란 이름으로 정의하지 않았다면 (CachingConfigurer
참고) 스프링 부트는 아래 provider를 찾아본다 (나타낸 순서대로):
- Generic
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan 등)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- 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는 원하는 만큼 만들 수 있으며,@Order
나Ordered
를 사용해 순서를 지정해도 된다.
Generic
컨텍스트가 org.springframework.cache.Cache
빈을 최소 하나라도 정의한다면 Generic 캐시를 사용한다. Cache
타입 빈을 전부 래핑하는 CacheManager
를 생성한다.
JCache (JSR-107)
JCache는 클래스패스에 javax.cache.spi.CachingProvider
가 있을 때 부트스트랩되며 (즉, 클래스패스에 JSR-107과 호환되는 캐시 라이브러리가 있을 때), JCacheCacheManager
는 spring -boot-starter-cache
“스타터”가 제공한다. 다양한 호환 라이브러리를 사용할 수 있으며, 스프링 부트는 Ehcache 3, Hazelcast, Infinispan을 위한 의존성 관리를 제공한다. 필요하면 다른 호환 라이브러리도 추가할 수도 있다.
둘 이상의 provider가 있을 수도 있는데, 이럴 땐 provider를 명시적으로 지정해줘야 한다. JSR-107 표준에선 설정 파일 위치를 정의하는 표준화된 방법은 없지만, 스프링 부트에선 구현체에 따른 캐시 설정도 최대한 수용한다:
# 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
를 커스텀하는 방법은 두 가지가 있다:
spring.cache.cache-names
프로퍼티를 설정하면 기동 시에 캐시를 생성할 수 있다. 커스텀javax.cache.configuration.Configuration
빈을 정의하면 커스텀할 때 이 빈을 사용한다.CacheManager
레퍼런스를 가지고org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer
빈을 호출하기 때문에 전체를 커스텀할 수 있다.
표준
javax.cache.CacheManager
빈을 정의하면, 추상화에서 사용하는org.springframework.cache.CacheManager
구현체로 자동으로 래핑된다. 여기에는 더 이상의 커스텀은 적용되지 않는다.
EhCache 2.x
EhCache 2.x는 클래스패스 루트에 ehcache.xml
이란 파일이 있을 때 사용한다. EhCache 2.x를 발견하면 spring-boot-starter-cache
“스타터”에서 제공하는 EhCacheCacheManager
를 통해 캐시 매니저를 부트스트랩한다. 다음 예제처럼 다른 설정 파일을 제공할 수도 있다:
spring.cache.ehcache.config=classpath:config/another-config.xml
spring:
cache:
ehcache:
config: "classpath:config/another-config.xml"
Hazelcast
스프링 부트는 Hazelcast를 범용적으로 지원하고 있다. HazelcastInstance
하나가 자동 설정되면, 자동으로 CacheManager
로 래핑한다.
Infinispan
Infinispan에선 디폴트 설정 파일 위치가 없기 때문에 직접 명시해줘야 한다. 명시하지 않으면 디폴트 부트스트랩을 사용한다.
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
캐시를 생성한다:
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
캐시를 생성한다:
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
프로퍼티로 기동 시 생성할 수 있으며, 다음 중 하나를 통해 커스텀할 수 있다 (나타낸 순서대로):
spring.cache.caffeine.spec
으로 캐시 스펙 정의com.github.benmanes.caffeine.cache.CaffeineSpec
빈 정의com.github.benmanes.caffeine.cache.Caffeine
빈 정의
예를 들어 아래 설정에선 최대 크기가 500, ttl이 10분인 cache1
, cache2
캐시를 생성한다:
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
프로퍼티를 설정하면 사용할 수 있는 캐시 리스트를 제한할 수 있다. 예를 들어 cache1
와 cache2
캐시만 사용하고 싶다면 cache-names
프로퍼티를 다음과 같이 설정해라:
spring.cache.cache-names=cache1,cache2
spring:
cache:
cache-names: "cache1,cache2"
이렇게 설정했을 때 애플리케이션이 여기 나열하지 않은 캐시를 사용하면, 해당 캐시가 필요한 런타임엔 실패하게 되지만, 기동 시에는 실패하지 않는다. 선언하지 않은 캐시를 사용할 때의 동작은 “실제” 캐시 provider의 동작 방식과 유사하다.
None
설정에 @EnableCaching
이 있으면 적절한 캐시 설정도 찾기 마련이다. 특정 환경에선 캐시를 전부 비활성화해야 한다면, 다음 예제처럼 캐시 타입을 none
으로 강제해서 no-op 구현체를 사용해라:
spring.cache.type=none
spring:
cache:
type: "none"
Next :Messaging
스프링 부트로 JMS, AMQP, Apache Kafka 메세지 시스템 자동 설정하기. 임베디드 카프카 브로커를 사용하는 방법도 함께 설명합니다.
전체 목차는 여기에 있습니다.