기타/개발일기

[Spring Boot] Redis로 캐싱 처리 적용하기

hu6r1s 2024. 3. 24. 13:19

Redis 정의

이전에 Spring Boot에서 Redis를 사용하는 방법에 대해서 간단하게 포스팅했다.

Key, Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템 (DBMS)이다.

데이터베이스, 캐시, 메세지 브로커로 사용되며 인메모리 데이터 구조를 가진 저장소이다.

Redis 장점

높은 성능

  • Redis는 인메모리 데이터 저장소로 작동하므로 빠른 속도로 데이터를 처리할 수 있다.
  • 비동기 방식으로 데이터를 처리하므로 성능이 향상된다.

데이터 구조 다양성

  • Redis는 다양한 데이터 구조를 지원하므로, String, List, Hash, Set 등을 통해 데이터를 쉽게 처리할 수 있다.

데이터 복제 및 분산

  • Redis는 데이터 복제를 지원한다.
  • 클러스터링을 통해 데이터를 분산 처리할 수 있다.

데이터 안정성 (영속성 유지)

  • Redis는 스냅샷 및 AOF 방식을 지원하여 데이터 손실을 방지할 수 있다.

캐시 정의

사용자에 입장에서 데이터를 더 빠르게, 더 효율적으로 액세스를 할 수 있는 임시 데이터 저장소를 뜻한다. 대부분의 어플리케이션에서 속도 향상을 위해 캐시를 사용한다.

구현 방법

Redis로 캐싱 처리를 구현하기 위해서 먼저 Redis를 사용하기 위해 설정을 해줘야 한다.

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

Spring Boot에서 제공해주는 Redis 라이브러리가 있다.

기본적으로 spring-boot-redis는 Lettuce를 사용한다.

RedisConfig

@Configuration
@EnableCaching
public class RedisConfig {

  @Value("${spring.data.redis.host}")
	@@ -22,11 +30,28 @@ public RedisConnectionFactory redisConnectionFactory() {
  }

  @Bean
  public RedisTemplate<String, Object> redisTemplate() {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    redisTemplate.setConnectionFactory(redisConnectionFactory());
    return redisTemplate;
  }

  @Bean
  public CacheManager cacheManager() {
    RedisCacheManager.RedisCacheManagerBuilder builder =
        RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());

    RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
            new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
        .disableCachingNullValues()
        .entryTtl(Duration.ofMinutes(10L));

    builder.cacheDefaults(configuration);

    return builder.build();
  }
  
}

 

Redis로 캐싱을 하기 위해서 설정을 해줘야 하는 것이 있다.

CacheManager를 통해서 데이터를 캐시(메모리)에 저장한다.

우리는 Redis를 통해서 캐싱처리를 할 것이기 때문에 CacheManager를 Redis 기준으로 설정해야 한다.

그리고 캐싱을 활성화하기 위해서 EnableCaching 어노테이션을 사용해준다.

캐싱 적용

@Override
@Cacheable(value = "Card", key = "#cardId", cacheManager = "cacheManager", unless = "#result == null")
public CardDetailsResponseDto getCard(Long cardId) {
Card card = cardRepository.findById(cardId).orElseThrow(
    () -> new EntityNotFoundException("카드 없음")
    ...
}

@Override
@Cacheable(value = "CardList", key = "'all'", cacheManager = "cacheManager", unless = "#result == null")
public List<CardResponseDto> getCards() {
return cardRepository.getQueryCards();
}

@Override
@CachePut(value = "Card", key = "#cardId", cacheManager = "cacheManager")
public void updateCard(Long cardId, CardUpdateRequestDto cardUpdateRequestDto, Long userId) {
User user = userService.findUser(userId);
Card card = cardRepository.findById(cardId).orElseThrow(
...
}

@Override
@CacheEvict(value = "Card", key = "#cardId", cacheManager = "cacheManager")
public void deleteCard(Long cardId, Long userId) {
User user = userService.findUser(userId);
Card card = cardRepository.findById(cardId).orElseThrow(
...

 

@Cacheable 어노테이션을 사용해서 동일한 파리 미터로 메서드를 호출 이력과 반환 결과가 캐시에 저장되어 있으면, 캐시를 사용한다.

value와 key를 저렇게 설정하면 `CardList::all`, `Card::{cardId}` 형식으로 등록된다.

@CachePut 어노테이션을 사용하면 캐시에 저장된 결괏값을 업데이트한다.

이것을 사용하지 않았다면 데이터를 수정했을 때, 캐시에 값을 계속 새로 저장한다면 쌓이게 되면서 성능이 좋지 않을 것이다.

@CacheEvict 어노테이션을 사용하면 캐시에 저장된 호출 이력 및 결과 값을 삭제 한다.

적용 비교

캐싱 적용 전

 

캐싱을 적용하기 전에는 응답하는 데 걸린 시간이 242ms로 걸린 것을 확인된다.

캐싱을 적용한다면 응답 시간이 얼마나 줄어들까?

캐싱 적용 후

 

캐싱을 적용하고 나면 응답 시간이 12ms인 것을 확인할 수 있다.

`약 20배` 정도의 속도 차이를 확인할 수 있다.

지금은 242ms라서 얼마 안 걸린다고 생각할 수 있지만, 데이터가 더 많아진다면 속도는 그만큼 느려질 것이고 캐싱를 적용하는 것이 효율적인 것을 인지할 수 있을 것이다.