Redis로 캐싱 구현하기

su_under·2024년 6월 18일
0

이번 포스팅에서는 Redis로 캐싱을 구현하는 방법에 대해서 알아보겠다.
현재 진행하고 있는 OMD 프로젝트에서 매번 데스크 셋업 전체를 조회하는 것은 비효율적이라는 생각이 들어 캐싱을 적용하기로 했다.

1. 의존성 추가

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

먼저 의존성을 추가하고 application.yml에 다음과 같이 작성한다.

spring:
  data:
    redis:
      host: redis
      port: 6379
      ssl:
        enabled: true

2. RedisConfig 작성

RedisConfig 파일을 작성하여 Redis를 사용하기 위한 설정을 정의해준다. Redis 서버와의 연결, 데이터 직렬화, 캐시 관리를 설정해주었는데 각자 상황에 맞게 작성하면 된다.

@Configuration
public class RedisConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
    }

    @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 RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory){
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(connectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .withInitialCacheConfigurations(redisCacheConfigurationMap)
                .build();
    }
}

3. 캐싱 적용

마지막으로 캐싱을 적용해보자. 캐싱을 적용하고 싶은 메소드에 @Cacheable 어노테이션을 적용한다. 나는 page, limit, criteria 파라미터를 조합하여 캐시 키를 생성했다. 이로써 동일한 파라미터로 메서드가 호출되면 캐시된 결과를 반환하게 된다.

새로운 게시글이 생성된 후 게시글 목록 조회를 했을 경우에 최신 데이터를 반영해야 하기 때문에 @CacheEvict 적용하여 게시글이 생성될 때마다 게시글 목록 조회와 관련된 모든 캐시 항목을 제거하여 이후의 조회 요청이 최신 데이터를 반영하도록 하였다.

  	// 게시글 생성
    @Transactional
    @CacheEvict(value = "posts", allEntries = true)
    public Post savePost(PostRequest request) {
        // 게시글 생성 로직
        return post;
    }
    
    // 게시글 목록 조회
    @Transactional(readOnly = true)
    @Cacheable(value = "posts", key = "#page + '-' + #limit + '-' + #criteria")
    public List<PostPreviewResponse> list(Integer page, Integer limit, Integer criteria) {
        // 게시글 목록 조회 로직
    }

4. 결과

캐싱 전에는 10번의 호출을 했을 때 평균적으로 30초가 걸렸다면 캐싱 후에는 평균적으로 14초가 걸렸다. 따라서 지연 시간이 대략 50%정도 감소했다.


❗️ 캐싱 구현 과정에서 있었던 오류

Could not write JSON: Java 8 date/time type java.time.LocalDateTime not supported by default: add Module

게시글 요청 응답 DTO에서 LocalDateTime 을 사용하고 있었는데, 해당 오류는 Java 8의 LocalDateTime 타입이 기본적으로 JSON 직렬화/역직렬화를 지원하지 않기 때문에 발생하는 문제였다. 이 문제를 해결하기 위해 LocalDateTime을 지원하는 모듈을 추가해야 했다. 따라서 다음과 같은 의존성을 추가하였다. jackson-datatype-jsr310 모듈은 Java 8 날짜 및 시간 API 타입을 직렬화 및 역직렬화할 수 있도록 지원한다.

implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'

하지만 위의 의존성을 추가해도 문제가 해결되지 않았다. 찾아보니 직렬화, 역직렬화 어노테이션을 추가해 주면 문제가 해결된다는 사례를 보고 나도 동일하게 적용해 보았다.


public class PostResponse {
    ...

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul")
    private final LocalDateTime createdAt;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul")
    private final LocalDateTime updatedAt;

}

위와 같이 @JsonSerialize(using = LocalDateTimeSerializer.class) , @JsonDeserialize(using = LocalDateTimeDeserializer.class) 을 추가해주었더니 문제가 해결되었다.


직렬화 및 역직렬화 과정을 명시적으로 정의함으로써, Jackson 라이브러리가 LocalDateTime 타입의 데이터를 처리할 때 발생할 수 있는 오류를 방지하고, 원하는 형식과 타임존에 맞게 데이터를 정확하게 변환할 수 있게 되었기 때문에 오류가 해결 된 것 같다.

profile
솨의 개발일기

2개의 댓글

comment-user-thumbnail
2024년 7월 5일

너무 대충 올리신거 같은데여..

1개의 답글