[Spring] Spring Cache에 대해

김강욱·2024년 4월 20일
1

Spring

목록 보기
1/17
post-thumbnail

캐시

캐시란 자주 사용되는 데이터의 접근을 빠르게 할 수 있도록 나중의 요청을 대비하여 데이터를 저장해두는 임시 저장소입니다.

캐시된 데이터를 사용함으로써 실제 데이터로의 접근 시간을 절약할 수 있고 이로 인해 시스템의 성능이 향상될 수 있기 때문에 유용하게 사용됩니다.

예를 들어, Redis 저장소에 자주 사용되는 데이터를 캐싱을 하고 해당 데이터 조회 요청이 들어왔다고 가정해보겠습니다. 해당 데이터를 Redis에 캐싱된 데이터를 읽어오기 때문에 실제 DB까지 해당 요청이 전달되지 않아 접근 시간을 절약할 수 있고 DB 부하를 낮출 수 있다는 장점이 있습니다.

그렇다면 캐시는 어디에 사용하는 것이 좋을까요?

  • 클라이언트에게 전달되는 값이 동일할 때
  • 빈번하게 호출될 때
  • 한 번 처리할 때 많은 서버 리소스를 요구할 때

예시로 공지사항, 조회수, 랭킹 등에서 캐시를 많이 적용합니다.

캐시를 적용하지 말아야하는 경우는 아래와 같습니다.

  • 실시간으로 정확성을 요구하는 경우
  • 빈번하게 데이터 변경이 일어나는 경우

이러한 캐시도 고려해야할 사항이 많습니다. 캐시 저장소와 실제 데이터 저장소 간의 데이터 일관성을 지키기 위해 데이터 동기화 시점, 동기화 방법 등을 잘 조율하여 선택하여야 합니다.

스프링 캐시(Spring Cache)

스프링에서 스프링 AOP 기반으로 캐시가 동작하며 어노테이션으로 AOP를 설정할 수 있어 간편하게 사용할 수 있습니다. @Cacheable, @CachePut, @CacheEvict 등을 사용하면 쉽게 캐시를 적용할 수 있습니다.

스프링 설정

  • build.gradle
implementation 'org.springframework.boot:spring-boot-starter-cache

Spring Boot Starter Cache란?

자주 사용되는 데이터를 캐시 메모리에 저장하여 빠른 검색을 가능하게 하는 기능을 제공합니다.

해당 캐시에 저장되는 데이터를 CacheManager를 통하여 메모리 뿐만 아니라 디스크, 데이터 베이스, 클라우드 서비스와 같은 다른 저장소에도 저장 가능하도록 동작합니다.

API 캐시를 통해서 DB로부터 반복적으로 데이터를 조회해오는 일에 대해 최초 데이터를 조회해온 뒤 이후 요청에 대해 캐시에서 데이터를 조회해옴으로써 API의 성능을 올리며 응답 시간을 단축하는 효율성을 가져옵니다.

Cache Manager

캐시 추상화에서는 캐시 기술을 지원하는 캐시 매니저를 Bean으로 등록해야 합니다.

  • ConcurrentMapCacheManager: JRE에서 제공하는 ConcurrentHashMap을 캐시 저장소로 사용할 수 있는 구현체입니다. 캐시 정보를 Map 타입으로 메모리에 저장해두기 때문에 빠르고 별다른 설정이 필요 없다는 장점이 있지만, 실제 서비스에서 사용하기엔 기능이 빈약합니다.
  • SimpleCacheManager: 기본적으로 제공하는 캐시가 없습니다. 사용할 캐시를 직접 등록하여 사용하기 위한 캐시 매니저 구현체입니다.
  • EhCacheCacheManager: Java에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시 매니저 구현체입니다.
  • CaffeineCacheManager: Java 8로 Guava 캐시를 재작성한 Caffeine 캐시 저장소를 사용할 수 있는 구현체입니다. EhCache와 함께 인기 있는 매니저인데, 이보다 좋은 성능을 갖는다고 합니다.
  • JCacheCacheManager: JSR-107 표준을 따르는 JCache 캐시 저장소를 사용할 수 있는 구현체입니다.
  • RedisCacheManager: Redis를 캐시 저장소로 사용할 수 있는 구현체입니다.
  • CompositeCacheManager: 한 개 이상의 캐시 매니저를 사용할 수 있는 혼합 캐시 매니저입니다.

Spring Boot Cache 어노테이션

Annotation설명
@EnableCachingSpring Boot Cache를 사용하기 위해 ‘캐시 활성화’를 위한 어노테이션을 의미한다
@CacheConfig캐시정보를 ‘클래스 단위’로 사용하고 관리하기 위한 어노테이션을 의미한다
@Cacheable캐시정보를 메모리 상에 ‘저장’하거나 ‘조회’해오는 기능을 수행하는 어노테이션이다
@CachePut캐시 정보를 메모리상에 ‘저장’하며 존재 시 갱신하는 기능을 수행하는 어노테이션이다
@CacheEvict캐시 정보를 메모리상에 ‘삭제’하는 기능을 수행하는 어노테이션이다
@Caching여러 개의 ‘캐시 어노테이션’을 함께 사용할 때 사용하는 어노테이션이다


주요 어노테이션 동작 흐름

@EnableCaching

@Cacheable과 같은 애노테이션 기반의 캐시 기능을 사용하기 위해서는 먼저 별도의 선언이 필요합니다.

@EnableCaching
@Configuration
public class CacheConfig {
    
    @Bean
    public RedisConnectionFactory basicCacheRedisConnectionFactory() {
    	// ...
    }
    
    @Bean
    public CacheManager cacheManager() {
    	RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Object.class)));

        Map<String, RedisCacheConfiguration> configurations = new HashMap<>();
        configurations.put("hotelCache", defaultConfig.entryTtl(Duration.ofMinutes(30)));
        configurations.put("hotelAddressCache", defaultConfig.entryTtl(Duration.ofDays(1)));

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(basicCacheRedisConnectionFactory())
                .cacheDefaults(defaultConfig)
                .withInitialCacheConfigurations(configurations)
                .build();
    }
}

@Cacheable

  • 캐시 조회 및 저장 기능: 특정 메서드의 결과를 캐시에 저장하고, 동일한 요청이 들어올 때 메서드를 실행하지 않고 캐시에서 결과를 반환합니다.
  • 캐시 존재 시: 메서드를 호출하지 않고 캐시된 결과를 바로 반환합니다.
  • 캐시 미 존재 시: 메서드를 호출하고, 그 결과를 캐시에 저장한 뒤 반환합니다.

예시 코드

// 캐시 저장
  @Cacheable("memberCacheStore")
  public Member cacheable(String date) {
    System.out.println("cacheable 실행");
    ...
    return member;
  }

  // 캐시 저장 (Key를 지정한 경우)
  @Cacheable(value = "memberCacheStore", key = "#member.name")
  public Member cacheableByKey(Member member) {
    System.out.println("cacheable 실행");
    ...
    return member;
  }

  // 조건부 캐시 저장 (With Condition)
  @Cacheable(value = "memberCacheStore", key = "#member.name", condition = "#member.name.length() > 5")
  public Member cacheableWithCondition(Member member) {
    System.out.println("cacheableWithCondition 실행");
    ...
    return member;
  }

1) key를 지정하지 않은 경우

  • 파라미터인 date 값에 2023-01이 처음으로 입력된 경우 파라미터 2023-01에 대한 캐싱된 데이터가 없으므로 메서드가 실행됩니다.
  • 반환 값인 member가 캐시 저장소에 저장됩니다.
  • 다시 2023-01을 파라미터에 넣고 요청했을 시 캐시 저장소에 저장된 member 값을 반환하고 해당 메서드는 실행되지 않습니다.

2) key를 지정하는 경우

  • name이 "exam"인 member 객체가 처음 들어오면 캐싱 전이므로 해당 메서드가 실행되고 해당 member 객체 데이터를 캐싱합니다.
  • 이후 "exam"이라는 name을 가진 memeber 객체를 파라미터로 다시 요청하게 되면 캐싱된 member 데이터 객체가 반환되고 해당 메서드는 실행되지 않습니다.

3) 조건부 캐싱

  • @Cacheable 어노테이션에 condition 속성을 통해 적용 가능합니다.
  • 해당 예시에서는 member 객체의 name 필드 값의 길이가 5를 초과하는 경우에만 캐싱되도록 설정하였습니다.

@CachePut

  • 캐시 저장 기능: 메서드의 결과를 무조건 캐시에 저장합니다. 메서드가 호출될 때마다 실행되며, 캐시를 최신 상태로 유지합니다.
  • 캐시 존재 여부에 관계없이: 메서드 호출 후 실행되고, 결과를 캐시에 저장합니다.

예시코드

// 캐시 저장 (Cacheable과 유사하게 실행 결과를 캐시에 저장하지만, 조회 시에 저장된 캐시 내용을 사용하지는 않고, 항상 메소드의 로직을 실행한다는 점에서 다르다.)
  @CachePut(value = "memberCacheStore", key = "#member.name")
  public Member cachePut(Member member) {
    System.out.println("cachePut 실행");
    ...
    return member;
  }

@CacheEvict

  • 캐시 삭제 기능: 지정된 캐시를 삭제합니다. 캐시를 무효화할 때 사용합니다.
  • beforeInvocation = true: 메서드 호출 전에 캐시를 삭제합니다. 이 옵션이 true일 경우, 메서드 실행 도중 예외가 발생해도 캐시는 이미 삭제된 상태입니다.
  • beforeInvocation = false (기본값): 메서드 호출 후에 캐시를 삭제합니다. 메서드가 성공적으로 완료된 후에 캐시 삭제가 이루어집니다.

예시 코드

// 캐시 제거
  @CacheEvict("memberCacheStore")
  public Member cacheEvict(String date) {
    System.out.println("cacheEvict 실행");
    ...
    return null;
  }

  // name 키 값을 가진 캐시만 제거
  @CacheEvict(value = "memberCacheStore", key = "#member.name")
  public Member cacheEvictByKey(Member member) {
    System.out.println("cacheEvictByKey 실행");
    ...
    return member;
  }

  // 캐시에 저장된 값을 모두 제거할 필요가 있을 때
  @CacheEvict(value = "memberCacheStore", allEntries = true)
  public Member cacheEvictAllEntries() {
    System.out.println("cacheEvictAllEntries 실행");
    ...
    return null;
  }

  // beforeInvocation 속성으로 메서드 실행 이후(기본값)나 이전에 제거를 해야하는지 지정할 수 있다.
  @CacheEvict(value = "memberCacheStore", beforeInvocation = true)
  public Member cacheEvictBeforeInvocation() {
    System.out.println("cacheEvictBeforeInvocation 실행");
    ...
    return null;
  }

참고 자료
velog.io/@songs4805/Spring-Cache에 대해 알아보자
[Spring] 스프링 캐시 알아보기

profile
TO BE DEVELOPER

0개의 댓글

관련 채용 정보