diff --git a/src/main/java/org/runnect/server/config/redis/RedisConfig.java b/src/main/java/org/runnect/server/config/redis/RedisConfig.java index 7cd9b7c..8491f0b 100644 --- a/src/main/java/org/runnect/server/config/redis/RedisConfig.java +++ b/src/main/java/org/runnect/server/config/redis/RedisConfig.java @@ -1,13 +1,21 @@ package org.runnect.server.config.redis; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; @EnableCaching @Configuration @@ -29,5 +37,22 @@ public ListOperations listOperations( RedisTemplate redisStringTemplate) { return redisStringTemplate.opsForList(); } + + // 공개 코스 조회 API(전체 페이지 수/마라톤/추천 목록) 응답 캐싱용. + // TTL을 짧게(1분) 두어, 코스가 추가/삭제돼도 오래된 응답이 너무 길게 남지 않도록 함. + @Bean + public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(1)) + .disableCachingNullValues() + .serializeKeysWith(RedisSerializationContext.SerializationPair + .fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair + .fromSerializer(new GenericJackson2JsonRedisSerializer())); + + return RedisCacheManager.builder(redisConnectionFactory) + .cacheDefaults(cacheConfiguration) + .build(); + } } diff --git a/src/main/java/org/runnect/server/publicCourse/service/PublicCourseService.java b/src/main/java/org/runnect/server/publicCourse/service/PublicCourseService.java index 0fb8248..9d6956a 100644 --- a/src/main/java/org/runnect/server/publicCourse/service/PublicCourseService.java +++ b/src/main/java/org/runnect/server/publicCourse/service/PublicCourseService.java @@ -38,6 +38,7 @@ import org.runnect.server.user.exception.userException.NotFoundUserException; import org.runnect.server.user.repository.UserRepository; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -63,6 +64,8 @@ private void setMARATHON_PUBLIC_COURSE_IDS(String MARATHON_PUBLIC_COURSE_ID) { .map(Long::parseLong).collect(Collectors.toList()); } + // 전체 유저에게 동일한 값이라 캐시 키에 별도 파라미터가 필요 없음 + @Cacheable(value = "publicCourseTotalPageCount") public GetPublicCourseTotalPageCountResponseDto getPublicCourseTotalPageCount() { Long totalPublicCourseCount = publicCourseRepository.countBy(); if (totalPublicCourseCount % PAGE_SIZE != 0) { @@ -71,6 +74,10 @@ public GetPublicCourseTotalPageCountResponseDto getPublicCourseTotalPageCount() return GetPublicCourseTotalPageCountResponseDto.of(totalPublicCourseCount / PAGE_SIZE); } + // 응답에 유저별 스크랩 여부(isScrap)가 섞여있어 userId를 캐시 키에 포함함 + // (그만큼 유저별로 캐시가 나뉘어서, 여러 유저가 섞인 실트래픽에서는 히트율이 + // 이번 부하테스트(단일 유저 반복 호출)만큼 극적이진 않을 수 있음) + @Cacheable(value = "marathonPublicCourse", key = "#userId") public GetMarathonPublicCourseResponseDto getMarathonPublicCourse(Long userId) { //1. 받은 userId가 유저가 존재하는지 확인 RunnectUser user = userRepository.findById(userId) @@ -150,6 +157,8 @@ public SearchPublicCourseResponseDto searchPublicCourse(Long userId, String keyw } + // marathonPublicCourse와 동일한 이유로 userId를 캐시 키에 포함 + @Cacheable(value = "recommendPublicCourse", key = "#userId + '_' + #pageNo + '_' + #sort") public RecommendPublicCourseResponseDto recommendPublicCourse(Long userId, Integer pageNo, String sort) { //1. 받은 userId가 유저가 존재하는지 확인 RunnectUser user = userRepository.findById(userId)