본문 바로가기

TIL

[Spring][Redis] RedisTemplate을 활용한 캐싱 처리

 

[Spring][Redis] Redis 캐싱 기능을 활용한 조회 성능 개선 (CacheManager)

데이터 캐싱의 개념에 대해 알아보고, Redis의 캐싱 기능을 활용하여조회 성능을 개선하는 예제를 포함한 포스팅입니다.✅ 캐싱이란?캐시(Cache)는 데이터나 값을 저장하는 임시 저장소로, 데이터

zapzook.tistory.com

 

 

이전 포스팅에 이어서, 본 포스팅에선 CacheManager를 활용한 방식이 아닌,

RedisTemplate을 활용한 캐싱을 적용해 볼 것이다.

RedisTemplate은 Redis 데이터 구조에 접근하고, CRUD 작업을 수행할 수 있도록 도와주는 고수준의 추상화된 템플릿이다.

 

 CacheManager VS RedisTemplate

 

📌 CacheManager을 활용한 캐싱

장점

  1. 간결한 코드:
    • @Cacheable, @CachePut, @CacheEvict 등의 애너테이션을 활용하여 간결하게 캐싱 로직을 적용 가능
    • 비즈니스 로직과 캐싱 로직이 분리되어 코드가 깔끔
  2. 일관성 및 유지보수성:
    • 캐시 관리가 중앙 집중화되어 일관된 캐시 정책을 유지할 수 있음
    • 캐시 설정과 관리가 표준화
    • 캐싱 로직이 표준화되어 유지보수가 용이

단점

  1. 제한된 유연성:
    • 세부적인 캐싱 로직을 커스터마이즈하기 어려움
    • 복잡한 캐시 키 관리나 조건부 캐싱 로직을 구현하기 어려움
  2. 추상화된 접근:
    • 캐시의 동작 방식이 추상화되어 있어, 캐시 미스 시의 동작 등을 세밀하게 제어하기 어려움
    • 기본 설정 외에 세부적인 캐싱 전략을 적용하려면 추가적인 설정이 필요

요약

간단한 캐싱 로직을 구현하고자 할 때, 캐시 설정과 관리의 일관성을 유지하고자 할 때 사용하면 좋음

 

📌 RedisTemplate을 활용한 캐싱

장점

  1. 유연성 및 제어력:
    • 직접적인 Redis 접근을 통해 키 생성, 만료 시간 설정, 직렬화 방법 등 세부적인 제어가 가능
    • 캐시 데이터를 개별적으로 관리할 수 있어 복잡한 캐시 로직의 구현 가능
  2. 직관적인 키 관리:
    • 개발자가 직접 키를 생성하고 관리할 수 있으므로, 키의 형식을 세밀하게 제어할 수 있음
    • 캐시 키를 커스터마이즈하기 용이
  3. 특정 조건 처리 용이:
    • 특정 조건에 따라 캐시 데이터를 삽입하거나 삭제하는 등의 복잡한 로직을 쉽게 구현할 수 있음

단점

  1. 반복되는 코드:
    • 캐시를 설정하고 검증하는 코드가 반복적으로 작성될 수 있음
    • 캐싱 로직이 비즈니스 로직과 섞여 코드가 복잡해질 수 있음
  2. 표준화 부족:
    • 코드마다 캐싱 방식이 다를 수 있어 일관성이 떨어질 수 있음
    • 유지보수 시 각기 다른 캐싱 로직을 이해하고 관리해야 함
  3. 추가적인 오류 가능성:
    • 잘못된 키 관리나 TTL 설정으로 인해 버그가 발생할 가능성이 있음

 

요약

복잡하고 디테일한 캐싱 로직을 구현해야 하며, 세부적인 캐싱 제어가 필요한 경우 사용하면 좋음

 

 예제

📌 RedisConfig

@EnableRedisRepositories // redis 활성화
@EnableCaching
@Configuration
public class RedisConfig {


    @Value("${spring.data.redis.host}")
    private String host;

    @Value("${spring.data.redis.port}")
    private int port;

    // 연결정보
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

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

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

}

 

Config에 원하는 데이터 형식에 맞게 RedisTemplate을 설정해준다.

 

📌 서비스 레이어에 캐싱 적용

public Page<ItemResponse> pageItem(SearchRequest searchRequest, Pageable pageable) {
    String key = searchRequest.toString() + pageable;
    Page<ItemResponse> itemResponseList = listRedisTemplate.opsForValue().get(key);
    if (itemResponseList != null) {
        return itemResponseList;
    }
    Page<ItemResponse> itemList = itemRepository.pageItem(searchRequest, pageable);
    listRedisTemplate.opsForValue().set(key, itemList, 5, TimeUnit.SECONDS);
    return itemList;
}

 

위 코드처럼 RedisTemplate의 opsForValue 메서드를 통해 캐싱 로직을 서비스 레이어에 구현할 수 있다.

CacheManager를 활용한 방식에 비해 코드가 다소 복잡해지지만, 캐시 키 값, TTL, 캐싱 로직을 입맛에 맞게

세밀하게 설정할 수 있다는 장점이 있다.

 

 결론

필자의 경우엔 프로젝트의 여러 서비스 API에 캐싱이 적용되어 있는데,

CacheManager를 통해 캐싱 로직을 표준화 하여 사용하다가 별도의 TTL 값과 키 값이 필요한

서비스 API가 생겨서 따로 RedisTemplate을 활용해 캐싱을 적용하였다.

이처럼 요구사항에 맞게 필요에 따라 두 방식중 하나를 선택하여 캐싱을 적용해보면 좋을 것 같다.