본문 바로가기

spring

[Spring] RedisCacheManager에 대해서

들어가면서

스프링에서 캐시를 기능을 추상화하여 사용자가 쉽게 캐시를 이용할 수 있도록 @Cacheable과 같은 어노테이션을 제공하는데 캐시를 저장하는 저장소와 상호작용하는 인터페이스는 CacheManager라는 인터페이스가 담당한다.

레디스를 캐시로 사용하는 경우에는 CacheManager를 상속받은 레디스 전용 캐시매니저이다. 레디스에 어떻게 키를 저장하는지 궁금해서 간단한 테스트코드와 디버거를 이용해서 공부해보았다.

Spring boot starter redis 의존성을 추가하고 로컬에서 Redis를 가동하고 연결 설정까지 하고 실습을 진행했다. 실습한 스프링 부트의 버전은 3.2.1이다.

데이터 캐시(저장)

스프링이 제공하는 추상화를 이용하려면 우선 Cache 객체를 매니저를 통해서 조회해야한다.

@SpringBootTest
public class RedisCashManagerPracticeTest {

    @Autowired
    CacheManager manager;

    @DisplayName("execute redis put")
    @Test
    void cache1() {
        // given
        Cache cache = manager.getCache("cache-type");
        // when
        cache.put("key", "test value");
        // then
        // redis 에서 데이터 확인하기! 
    }
}

스프링 부트 테스트를 사용하여 의존성 설정을 부트에게 위임하고 만들어진 CacheManager를 주입받는다. 이때의 매니저는 RedisCacheManager이다.

테스트를 진행하고 레디스에 저장된 데이터를 확인한 결과는 다음과 같다.

캐시를 가져올 때의 값 `cache-type`과 데이터를 저장할 때, 사용한 키값의 조합으로 생성된 `cache-type::key`를 키로 가지고 값은 알 수 없는 쓰레기 값(아마 직렬화를 위한 클래스 정보라고 추정한다)과 실제 값이 저장된 것을 확인할 수 있었다.

데이터 조회

데이터 조회 역시 Cache 객체가 필요하다. 저장에서 처럼 캐시를 매니저를 통해서 조회하고 get..() 메서드를 통해서 조회할 수 있다. 기초적인 get()을 통해서 테스트를 해보았다.

방금 생성한 데이터를 조회해보자!

    @Order(2)
    @DisplayName("Redis get value")
    @Test
    void redisGetValue() {
        // given
        Cache cache = manager.getCache("cache-type");
        // when
        ValueWrapper value = cache.get("key");
        String stringValue = cache.get("key", String.class);
        String keyName = cache.getName();
        // then
        System.out.println("value = " + value.get());
        System.out.println("stringValue = " + stringValue);
        System.out.println("keyName = " + keyName);
    }

저장과정에서 저장한 데이터가 잘 조회됨을 테스트로 검증할 수 있다!!

하지만 조회과정에서 레디스와 연결이 불안정하여 실패한다면 어떻게 처리가 될까? 그래서 디버깅을 해본 결과, Cache 객체를 생성하는 manger.getCache()에서는 레디스가 다운되어도 정상적으로 실행되는 반면 캐시 객체에서 어떤 연산을 실행하는 코드에서는 에러가 발생한 것을 확인할 수 있었다.

Redis가 다운되었을 때, 발생한 에러 출력 중 일부..

존재하지 않은 키로 데이터를 조회하는 경우에는 어떻게 될까?

    @DisplayName("When get key that is not existed, Redis get value")
    @Test
    void redisGetKeyThatIsNotExisted() {
        // given
        Cache cache = manager.getCache("cache-type");
        // when
        ValueWrapper value = cache.get("invalid");
        String stringValue = cache.get("invalid", String.class);
        String keyName = cache.getName();
        // then
        System.out.println("stringValue = " + stringValue);
        System.out.println("keyName = " + keyName);
        then(value).isNull();
        then(stringValue).isNull();
    }

에러가 발생하지는 않고 null을 반환한다는 것을 알 수 있었다.

데이터 삭제

마지막으로 캐시된 데이터를 매니저가 삭제하는 방법을 알아보자!

      @Order(3)
    @DisplayName("delete cache")
    @Test
    void deleteCache() {
        // given
        Cache cache = manager.getCache("cache-type");
        // when
        cache.evict("key");
        ValueWrapper value = cache.get("key");
        String stringValue = cache.get("key", String.class);
        String keyName = cache.getName();
        // then
        System.out.println("stringValue = " + stringValue);
        System.out.println("keyName = " + keyName);
        then(value).isNull();
        then(stringValue).isNull();
    }

cache.evict()를 이용해서 삭제할 수 있다.

마치며

@Cacheable이 작동할 때, 사용하는 캐시 매니저를 통해서 어떻게 캐시를 저장, 조회, 삭제하는지 그 구현 방식을 알 수 있었다. 특히 RedisCacheManager가 어떻게 캐시를 관리하는지 알 수 있었다.
TTL은 Redis 매지저를 생성할 때, 설정할 수 있기 때문에 이런 부가적인 설정은 다음 포스팅에서 한번 알아볼까? 한다.
그리고 CacheResolver를 통해서 여러개의 캐시 매니저를 통한 전략을 구현할 수 있다고 들었는데 그 방법을 알아봐야겠다.