
💡 캐싱을 위해 Redis를 도입한 이유
위의 의사결정으로 인해 Redis를 사용하여 캐싱을 구현하려고 합니다.
필자의 프로젝트 내에서 캐싱을 적용해 볼만한 개소는 조회가 많이 발생하는 메인페이지의 상품 목록 입니다.
캐싱할 데이터의 효율이 올라가는 경우가 값이 변하지 않는 경우입니다.
이를 위해, 상품 정보 중 식품명, 식품 기능, 식품 형태, 식품 성분표 등의 고정된 데이터값만 캐싱하도록 구현하겠습니다.
조회 시 부하를 낮추기 위해서 Pagenation 되어 조회되도록 구현했습니다.
@Override
public ProductListDto getAllProducts(int nowPage, int pageSize) {
log.debug("상품 목록 조회 실행");
PageRequest pageRequest = PageRequest.of(nowPage, pageSize, Sort.by("createdAt").descending());
Page<ProductEntity> pageList = productRepository.findAll(pageRequest);
return ProductListDto.builder()
.totalPages(pageList.getTotalPages())
.totalProducts(pageList.getTotalElements())
.productList(pageList.stream().map(ProductEntity::entityToDtoList).toList())
.build();
}

위 그림에서 알 수 있듯이 응답 시간 평균은 36.22ms 입니다.
Redis 인프라 구축은 프로젝트 내 이미 구현되어 있기 때문에 별도로 구현하지는 않겠습니다.
Spring Data Redis에서 Spring Cache Abstraction의 구현체로 org.springframework.data.redis.core를 제공합니다.
Redis를 캐시 메모리로 사용하기 위해서는 RedisCacheManager 관련 설정 및 빈으로 등록해줘야 합니다.
@EnableCaching // 추가
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
...
}
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
.defaultCacheConfig()
.disableCachingNullValues() // null 값은 캐싱하지 않음
.entryTtl(Duration.ofMinutes(5)); // cache ttl
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(connectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
}
...
}
@Cacheable(value="PRODUCTS_CACHE", key="'nowPage:' + #nowPage")
@Override
public ProductListDto getAllProducts(int nowPage, int pageSize) {
log.debug("상품 목록 조회 실행");
PageRequest pageRequest = PageRequest.of(nowPage, pageSize, Sort.by("createdAt").descending());
Page<ProductEntity> pageList = productRepository.findAll(pageRequest);
return ProductListDto.builder()
.totalPages(pageList.getTotalPages())
.totalProducts(pageList.getTotalElements())
.productList(pageList.stream().map(ProductEntity::entityToDtoList).toList())
.build();
}

위 그림에서 알 수 있듯이 응답 시간 평균은 5.49ms 입니다.
위 결과를 확인해보면, 대략 85% 정도 개선됐습니다.