본문 바로가기

Framework/Spring

[Spring][Redis] Spring Cache Abstraction, @Cacheable, Redis Monitor 에 대해서 - 컴도리돌이

728x90

 

이전 포스팅에서 Redis 설정하는 방법과 그 안에서 사용한 메서드에 대해서 가볍게 포스팅을 해봤습니다. 이번에는 설정한 CacheManager을 통해서 스프링에서 캐싱을 어떻게 사용하는지 알아보려고 해요 😊

 


@Cacheable

캐싱은 많은 애플리케이션에서 성능을 향상하는 핵심적인 기술 중 하나이며, 그중에서도 스프링 프레임워크에서는 @Cacheable 어노테이션을 통해 메서드 호출의 결과를 캐싱하는 기능을 제공합니다. 이를 통해 반복적인 계산을 피하고 응답 시간을 줄일 수 있게 됩니다.

 

@Cacheable는 메서드의 특정 인자에 대한 결과 값을 캐시저장소에 저장하고 같은 인자에 대한 결과 값을 메서드를 실행하지 않고 캐싱 저장소에서 가져와 반환을 해줍니다. 

@Service
public class MyService {

    @Cacheable("book")
    public String findBookName(Long id) {
       ...
    }
}

 

위에 코드와 같이 작성하고 findBook을 메서드를 호출하면 redis에서는 어떻게 값을 저장할까요?

 

1712556655.881613 [0 127.0.0.1:60866] "GET" "book::3"
1712556655.914841 [0 127.0.0.1:60866] "SET" "book::3" "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" "PX" "2592000000"

 

공백을 기준으로 맨 처음에 있는 "1712556655.881613"와 같은 형식은 Timestamp(타임스탬프)으로, 각각 해당 요청이 수신된 시간을 나타냅니다. 보통은 유닉스 타임스탬프 형식으로 제공됩니다. 

 

그다음 문장은 "[0 127.0.0.1:60866]"이 표시되었는데, 이것은 클라이언트 정보를 나타내요. 여기서 "0"은 클라이언트 ID를 나타내며, "127.0.0.1:60866"은 클라이언트 IP주소와 포트번호를 나타냅니다. 이는 해당 요청을 보낸 클라이언트의 정보를 제공해 줘요.

 

그다음은 SET/GET과 같이 요청 유형을 나타내며, 이에 이어지는 매개변수는 해당 요청에 필요한 추가 정보를 제공합니다. 예를 들어 첫 번째 줄의 "GET" 요청을 먼저 확인할까요?

 

"GET" "book::3", "GET" 요청으로 "book::3"이라는 키에 대한 값을 조회합니다. 하지만 해당 값이 없기 때문에 메서드 내부 쿼리가 동작하게 되고, 결과 값을 레디스 캐시 서버에 저장하게 될 것입니다. 

 

다음 실행된 출력 값을 확인해 볼까요? 🤨

 

"book::3" "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" "PX" "2592000000"

 

결과 값으로는 원래 "컴도리돌이챕터1" 이라는 값을 반환했습니다. 제공된 값을 유니코드로 인코딩한 문자열이 "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" 입니다. 

 

그리고 "PX"이라는 옵션은 만료 시간을 밀리초 단위로 설정한다는 의미로, "milliseconds"를 나타냅니다. 마지막으로 2592000000은 만료 시간을 나타내며, 디폴트 값으로 2592000000 밀리초로 설정되어 있습니다. 이것은 약 30일에 해당되는 숫자입니다. 

 


 

[참고]

cacheNames과 value 옵션은 명칭만 다르며, 동일한 기능을 수행합니다. 

 

Cacheable (Spring Framework 6.1.5 API)

Annotation indicating that the result of invoking a method (or all methods in a class) can be cached. Each time an advised method is invoked, caching behavior will be applied, checking whether the method has been already invoked for the given arguments. A

docs.spring.io

public @interface Cacheable {
    @AliasFor("cacheNames")
    String[] value() default {};

    @AliasFor("value")
    String[] cacheNames() default {};
	
    ...
}

 


 

cacheNames, value

 

위에 코드에서 cacheNames option을 사용해서 "Books" 캐시 이름을 추가했습니다. 

 
// @Cacheable(value = {"book", "Books"})
@Cacheable(cacheNames = {"book", "Books"})
public String findBookName(Long id) {
   ...
}

 

그리고 동일하게 id 값을 3에 대해 조회를 하면 어떻게 될까요? 🤔

 

1712558257.528949 [0 127.0.0.1:60866] "GET" "book::3"

 

이미 위에 예시 코드에서 id 값 3에 대한 book 키 값을 저장했기 때문에 캐싱 처리되는 것을 확인할 수 있습니다. 그렇다면 조회하지 않은 4 값에 대해서는 어떻게 저장될까요?

 

1712558261.109660 [0 127.0.0.1:60866] "GET" "book::4"
1712558261.114142 [0 127.0.0.1:60866] "GET" "Books::4"

 

id 값 4에 대한 조회가 없기 때문에 캐시 서버에 저장하게 되는데 cacheName 옵션에 명시한 캐시 이름에 대해 각각 저장을 해줍니다. 캐시 이름을 여러 개로 저장하는 이유가 뭘까요? 여러 개의 캐시 이름을 사용하여 각각의 캐시에 대해 다른 설정을 적용함으로써, 캐시 데이터를 더욱 효율적으로 관리할 수 있습니다.

 


 

Key

key 옵션은 캐시에 저장된 데이터를 검색하기 위한 키를 지정하는 데 사용됩니다. 이 옵션을 사용하여 캐시에 저장된 데이터를 식별하는 키를 동적으로 생성할 수 있게 돼요

 

key 옵션을 설정하지 않으면 기본적으로 메서드의 모든 파라미터 값을 고려하여 키를 생성합니다. 그러기 때문에 경우에 따라 파라미터만을 고려하여 키를 생성하거나, 파라미터 외의 다른 정보를 활용하여 유니크한 키를 생성해야 합니다. 

 

@Cacheable(value = {"book", "Books"}, key = "'books_id:' + #id")
public String findBookName(Long id) {
  ...
}

 

위에 코드처럼 key 값을 SpEL을 사용하여 id 값을 참조하고, 추가적으로 문자열을 합쳐서 키로 지정할 수 있습니다. 다음은 books_id:3 키값에 대한 조회를 레디스 모니터에 출력된 내용입니다.

 

1712560662.237616 [0 127.0.0.1:60866] "GET" "book::books_id:3"
1712560662.242664 [0 127.0.0.1:60866] "GET" "Books::books_id:3"
1712560662.280208 [0 127.0.0.1:60866] "SET" "book::books_id:3" "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" "PX" "86400000"
1712560662.284968 [0 127.0.0.1:60866] "SET" "Books::books_id:3" "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" "PX" "86400000"

 


 

condition/ unless

condition 및 unless 옵션은 @Cacheable 어노테이션에서 캐시 동작을 제어하는 추가적인 옵션입니다. 

@Cacheable(value = {"book", "Books"}, key = "'books_id:' + #id" ,condition = "#id > 3")
public String findBookName(Long id) {
    ...
}

 

SpEl을 사용하여 조건식을 정의하며, 조건식이 'true'인 경우에만 캐시가 수행됩니다. 위에 코드에서는 id 값이 3 이상인 데이터만 캐싱 처리가 됩니다.

 

@Cacheable(value = {"book", "Books"}, key = "'books_id:' + #id" ,unless = "#id > 3")
public String findBookName(Long id) {
    ...
}

 

condition 옵션과 반대로 동작하며, 조건식이 'true'인 경우에 캐시가 수행되지 않습니다. 그러므로 위에 코드에서는 id 값이 3 이상이 아닌 데이터만 캐싱 처리가 됩니다. 

 


 

CacheManager

스프링에서는 메서드의 결과를 캐시에 저장하고, 해당 캐시를 효율적으로 관리하기 위해 CacheManager 옵션을 제공합니다.

전 포스팅에서 캐시 매니저 메서드를 다음과 같이 작성했었습니다. 🫣

@Configuration
@EnableCaching
public class RedisConfig implements CachingConfigurer {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
    }

}

 

그렇다면 작성한 메서드를 어떻게 적용하는지도 알아봐야겠죠? 😤 

 

@Cacheable(value = {"book", "Books"}, cacheManager = "cacheManager")
public String findBookName(Long id) {
    ...
}

 

위와 같이 Bean으로 등록한 캐시매니저 클래스 명을 옵션 값에 문자열 형태로 정의하면 됩니다. 너무 간단하죠 🥱🥱

그렇다면 제가 설정한 옵션에 대해서 잘 적용되는지 확인해 볼까요? 

 

1712560111.004290 [0 127.0.0.1:60866] "SET" "book::8" "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" "PX" "86400000"
1712560111.009215 [0 127.0.0.1:60866] "SET" "Books::8" "\"\xec\xbb\xb4\xeb\x8f\x84\xeb\xa6\xac\xeb\x8f\x8c\xec\x9d\xb4\xec\xb1\x95\xed\x84\xb01\"" "PX" "86400000"

 

조회하지 않은 Id 8 값에 대한 메서드를 호출해 보았습니다. 이전 출력 값과 거의 동일하고 마지막 유효 시간 값이 기존 값이랑 다릅니다. 😆

 

"PX" 옵션으로 86400000 밀리초(1일)로 TTL(유효 시간)이 설정된 것으로 보아, 캐시 매니저가 정상적으로 작동된 것을 확인할 수 있습니다. 

 

이번에 @Cacheable 어노테이션에 대해 모든 것을 알아 보았습니다. 

이 계기로 적절하게 캐시 명칭을 정의하고, 각각의 명칭에 따른 캐시 매니저를 정의하여, 캐시 값을 세분화하여 관리해보아야겠어요 🤔

 


 

 

MONITOR

Listens for all requests received by the server in real-time.

redis.io