[Spring] 스프링 캐시 + 스프링 레디스

maxxyoung·2024년 4월 9일
0

스프링은 조회 결과를 캐시 메모리에 저장해 놓고 다시 똑같은 요청이 온다면 저장해 놓은 조회 결과를 캐시 메모리에서 꺼내 돌려준다. 메소드를 또 실행하지 않고 저장해놓은 조회 결과를 돌려주므로써 조회의 성능을 끌어 올리 수 있다.
스프링 캐시에 대해 알아본 후 스프링 레디스 캐시에 대해 알아보자.

스프링 캐시

설정

스프링부트 의존성을 추가한다.

implementation("org.springframework.boot:spring-boot-starter-cache")

설정 파일을 작성한다.

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("addresses");
    }
}
  • @EnableCaching 어노테이션을 통해 스프링 애플리케이션의 캐시를 활성화 시킨다.
@Component
public class SimpleCacheCustomizer 
  implements CacheManagerCustomizer<ConcurrentMapCacheManager> {

    @Override
    public void customize(ConcurrentMapCacheManager cacheManager) {
        cacheManager.setCacheNames(asList("users", "transactions"));
    }
}
  • 스프링부트를 사용한다면 @EnableCaching과 함께 클래스 경로에 스타터 패키지가 존재하는 것만으로도 동일한 ConcurrentMapCacheManager가 등록되어 별로의 설정 클래스가 필요 없다.
  • 하나 이상의 CacheManagerCustomizer<T> 빈을 사용하여 자동 구성된 CacheManager를 사용자 정의할 수 있다.

캐시 어노테이션의 종류

@Cacheable

@Cacheable("addresses")
public String getAddress(Customer customer) {...}
  • @Cacheable을 통해 메소드의 결과를 캐싱할 수 있다.
  • 괄호안에 캐싱된 결과를 저장할 이름을 명시해주면 된다.
@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}
  • 캐시를 여러 개 쓰고 있다면 두 번째 파라미터에 캐시의 이름을 명시해주면 된다.

@CacheEvict

@CacheEvict(value="addresses", allEntries=true)
public String updateAddress(Customer customer) {...}
  • 캐시를 지울 때 사용한다.
  • allEntries=true일 경우 모든 캐시가 삭제된다.
  • 예를 들어 주소를 업데이트하는 로직에 사용할 경우 주소가 업데이트 될 때 캐시가 삭제된다. 후에 주소를 조회하는 로직에서 다시 캐싱한다.

@CachePut

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}
  • 항상 메소드를 실행하고 그 결과를 캐시에 저장한다.

@Caching

@Caching(evict = { 
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}
  • 캐시 관련 여러 어노테이션을 사용할 때 어노테이션들을 그룹지을 수 있다.

@CacheConfig

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {

    @Cacheable
    public String getAddress(Customer customer) {...}
  • 클래스 레벨에서 캐시를 설정할 수 있다.
  • 클래스에 종속되는 메소드들의 캐시 설정을 반복해서 작성할 필요가 없다.

조건과 캐시

조건 파라미터

@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}
  • condition의 조건에 통과하는 결과만 캐시한다.(고객이름이 Tom일 경우 캐시)

Unless 파라미터

@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}
  • 메소드의 결과 값이 unless의 조건을 충족하지 않아야 캐시한다.(결과값이 64 글자 이상이어야 캐시)

스프링 레디스 캐시

스프링은 다양한 캐시를 지원하지만 그 중 레디스에 대해 알아보자.

설정

설정 구현체

  • 설정은 RedisCacheManager, RedisCacheManagerBuilder, RedisCacheConfiguration 구현체를 통해 이루어진다.
    • RedisCacheManager 캐시별로 사용자 정의 구성을 허용한다. 캐시가 2개라면 manager도 2개
    • RedisCacheConfiguration을 통해 만료시간, prefix, RedisSerializer를 설정할 수 있다.
    • 스프링부트의 경우 starter-data-redis를 사용하면 RedisCacheManager가 기본 캐시 매니저로 자동 등록된다.

Lock

  • RedisCacheManager 기본적으로 락을 사용하지 않는다.
RedisCacheManager cacheMangager = RedisCacheManager
    .build(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory))
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    ...
  • 락을 사용하려면 위의 코드와 같이 RedisCacheWriter.lockingRedisCacheWriter()를 통해 설정할 수 있다.

접두사

// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixCacheNameWith("(͡° ᴥ ͡°)");

The following example shows how to set a computed prefix:

// computed key prefix
RedisCacheConfiguration.defaultCacheConfig()
    .computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);
  • 동적, 계산된 접두사를 만들 수 있다.(스프링 공식 문서 치곤 이모지 귀엽자냐...)

스프링 레디스와 레디스 명령어

  • 캐시 구현체들은 기본적으로 KEYSDEL 명령어를 사용한다.
  • KEYS명령어는 지정된 패턴의 모든 키를 한번에 반환하므로 키가 많을 때 성능이슈가 있다.
RedisCacheManager cacheManager = RedisCacheManager
    .build(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    ...
  • BatchStrategySCAN-based batch strategy로 바꾸어 RedisCacheWriter를 생성할 수 있다.
  • SCAN 명령어는 키를 반복적으로 스캔하면서 일치하는 키를 반환한다.
  • SCAN 전략은 배치 사이즈를 필요로한다. 위의 소스에서는 1000개 기준으로 가져온다.

캐시 만료

time-to-live (TTL)과 time-to-idle (TTI)의 차이점을 알아보자.

  • TTL: 설정한 시간이 지나면 데이터가 사라진다. 데이터를 읽거나 업데이트해도 시간은 연장되지 않는다.
  • TTI: 설정한 시간이 지나면 데이터가 사라지는건 같지만 그 전에 데이터를 읽거나 업데이트 한다면 시간은 다시 초기화된다.

스프링 레디스 캐시는 TTL만 지원하고 있다. TTI처럼 동작하기 위해서는 개발자가 로직을 작성해 주어야한다.

RedisCacheConfiguration fiveMinuteTtlExpirationDefaults =
    RedisCacheConfiguration.defaultCacheConfig().enableTtl(Duration.ofMinutes(5));
  • 위와 같은 설정을 통해 TTL을 설정할 수 있다.
enum MyCustomTtlFunction implements TtlFunction {

    INSTANCE;

    @Override
    public Duration getTimeToLive(Object key, @Nullable Object value) {
        // compute a TTL expiration timeout (Duration) based on the cache entry key and/or value
    }
}
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(MyCustomTtlFunction.INSTANCE);

RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
    .cacheDefaults(defaults)
    .build();
  • TTL을 사용자 커스텀할 수 있다.

bealdung cache tutorial
spring cache guide
spring redis cache

profile
오직 나만을 위한 글. 틀린 부분 말씀해 주시면 감사드립니다.

0개의 댓글