From d8329971d7694d3bf38c5cc5e3dc253b6cf86f99 Mon Sep 17 00:00:00 2001 From: CokeLee777 Date: Tue, 23 Jan 2024 12:02:30 +0900 Subject: [PATCH 1/2] [FIX] add redis compression --- build.gradle | 2 +- .../java/com/dailyon/snsservice/config/CacheConfig.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 49c9ad3..bafd01f 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ dependencies { // aws implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.4.RELEASE' - implementation group: 'io.github.dailyon-maven', name: 'daily-on-common', version: '0.0.6' + implementation group: 'io.github.dailyon-maven', name: 'daily-on-common', version: '0.1.1' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' diff --git a/src/main/java/com/dailyon/snsservice/config/CacheConfig.java b/src/main/java/com/dailyon/snsservice/config/CacheConfig.java index 6c13665..17da3c1 100644 --- a/src/main/java/com/dailyon/snsservice/config/CacheConfig.java +++ b/src/main/java/com/dailyon/snsservice/config/CacheConfig.java @@ -9,6 +9,9 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.time.Duration; import java.util.List; + +import dailyon.domain.utils.KryoRedisSerializer; +import dailyon.domain.utils.SnappyRedisSerializer; import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -69,7 +72,7 @@ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer( new StringRedisSerializer())) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( - postCountVOJackson2JsonRedisSerializer))) + new SnappyRedisSerializer(new KryoRedisSerializer<>())))) .withCacheConfiguration( "top4OOTD", RedisCacheConfiguration.defaultCacheConfig() @@ -80,6 +83,6 @@ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer( new StringRedisSerializer())) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( - top4OOTDVOJackson2JsonRedisSerializer)))); + new SnappyRedisSerializer>(new KryoRedisSerializer<>()))))); } } From 83546aee65241ac5d16f4de9cb4fbdd4aa9faea6 Mon Sep 17 00:00:00 2001 From: CokeLee777 Date: Tue, 23 Jan 2024 18:44:08 +0900 Subject: [PATCH 2/2] [FIX] fix serialize logic --- build.gradle | 2 +- .../cache/PostCountRedisRepository.java | 55 +++++++------- .../cache/Top4OOTDRedisRepository.java | 13 ++-- .../snsservice/config/CacheConfig.java | 21 +----- .../snsservice/config/RedisConfig.java | 9 ++- .../snsservice/scheduler/PostScheduler.java | 2 +- .../post/PostCountRedisRepositoryTest.java | 9 +-- .../service/CommentServiceTest.java | 8 +- .../service/PostLikeServiceTest.java | 73 +++++++------------ .../snsservice/service/PostServiceTest.java | 12 +-- 10 files changed, 85 insertions(+), 119 deletions(-) diff --git a/build.gradle b/build.gradle index bafd01f..39162ac 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ dependencies { // aws implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.4.RELEASE' - implementation group: 'io.github.dailyon-maven', name: 'daily-on-common', version: '0.1.1' + implementation group: 'io.github.dailyon-maven', name: 'daily-on-common', version: '0.1.2' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' diff --git a/src/main/java/com/dailyon/snsservice/cache/PostCountRedisRepository.java b/src/main/java/com/dailyon/snsservice/cache/PostCountRedisRepository.java index d7ee35d..f3529b3 100644 --- a/src/main/java/com/dailyon/snsservice/cache/PostCountRedisRepository.java +++ b/src/main/java/com/dailyon/snsservice/cache/PostCountRedisRepository.java @@ -2,33 +2,37 @@ import com.dailyon.snsservice.vo.PostCountVO; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor public class PostCountRedisRepository { - private final RedisTemplate redisTemplate; - private final ObjectMapper objectMapper; + private final RedisTemplate redisTemplate; @Cacheable(value = "postCount", key = "#key", unless = "#result == null") public PostCountVO findOrPutPostCountVO(String key, PostCountVO postCountVO) throws JsonProcessingException { - String stringValue = redisTemplate.opsForValue().get(key); - if (isInValidValue(stringValue)) { + PostCountVO cachedPostCountVO = redisTemplate.opsForValue().get(key); + if (isInValidValue(cachedPostCountVO)) { return postCountVO; } - return objectMapper.readValue(stringValue, PostCountVO.class); + return cachedPostCountVO; } @CachePut(value = "postCount", key = "#key") @@ -40,27 +44,28 @@ public PostCountVO modifyPostCountVOAboutLikeCount(String key, PostCountVO postC @CacheEvict(value = "postCount", key = "#key") public void deletePostCountVO(String key) {} - public List> findPostCountVOs(String cacheName) { - Set postIds = redisTemplate.keys(cacheName + ":*"); - if (postIds != null && !postIds.isEmpty()) { - return postIds.stream() - .map( - postId -> { - String stringValue = redisTemplate.opsForValue().get(postId); - PostCountVO postCountVO; - try { - postCountVO = objectMapper.readValue(stringValue, PostCountVO.class); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - return Map.of(postId, postCountVO); - }) - .collect(Collectors.toList()); - } - return null; + @Cacheable(value = "postCount", unless = "#result == null", key = "'*'") + public List> findPostCountVOs() { + Set allKeys = redisTemplate.keys("postCount::*"); + + // Fetch values for each key + List> allValues = + allKeys.stream() + .map( + key -> { + String postId = key.split("::")[1]; + PostCountVO cachedPostCountVO = redisTemplate.opsForValue().get(key); + if (isInValidValue(cachedPostCountVO)) { + return Map.of(postId, new PostCountVO(0, 0, 0)); + } + return Map.of(postId, cachedPostCountVO); + }) + .collect(Collectors.toList()); + + return allValues; } - private Boolean isInValidValue(String value) { + private Boolean isInValidValue(PostCountVO value) { return value == null; } } diff --git a/src/main/java/com/dailyon/snsservice/cache/Top4OOTDRedisRepository.java b/src/main/java/com/dailyon/snsservice/cache/Top4OOTDRedisRepository.java index eaa8700..24d8552 100644 --- a/src/main/java/com/dailyon/snsservice/cache/Top4OOTDRedisRepository.java +++ b/src/main/java/com/dailyon/snsservice/cache/Top4OOTDRedisRepository.java @@ -17,24 +17,21 @@ public class Top4OOTDRedisRepository { private final PostRepository postRepository; - private final RedisTemplate redisTemplate; - private final ObjectMapper objectMapper; + private final RedisTemplate> redisTemplate; @Cacheable(value = "top4OOTD", key = "#productId", unless = "#result == null") public List findOrPutTop4OOTDVO(String productId) throws JsonProcessingException { - String stringValue = redisTemplate.opsForValue().get(productId); - if (isInValidValue(stringValue)) { + List cachedTop4OOTDVOs = redisTemplate.opsForValue().get(productId); + if (isInValidValue(cachedTop4OOTDVOs)) { List posts = postRepository.findTop4ByOrderByLikeCountDesc(Long.parseLong(productId)); return posts.stream() .map(post -> new Top4OOTDVO(post.getId(), post.getPostImage().getThumbnailImgUrl())) .collect(Collectors.toList()); } - return objectMapper.readValue( - stringValue, - objectMapper.getTypeFactory().constructCollectionType(List.class, Top4OOTDVO.class)); + return cachedTop4OOTDVOs; } - private Boolean isInValidValue(String value) { + private Boolean isInValidValue(List value) { return value == null; } } diff --git a/src/main/java/com/dailyon/snsservice/config/CacheConfig.java b/src/main/java/com/dailyon/snsservice/config/CacheConfig.java index 17da3c1..e625ee6 100644 --- a/src/main/java/com/dailyon/snsservice/config/CacheConfig.java +++ b/src/main/java/com/dailyon/snsservice/config/CacheConfig.java @@ -2,7 +2,6 @@ import com.dailyon.snsservice.vo.PostCountVO; import com.dailyon.snsservice.vo.Top4OOTDVO; -import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; @@ -18,7 +17,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -47,19 +45,7 @@ public RedisCacheConfiguration redisCacheConfiguration(ObjectMapper objectMapper } @Bean - public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer( - ObjectMapper objectMapper) { - Jackson2JsonRedisSerializer postCountVOJackson2JsonRedisSerializer = - new Jackson2JsonRedisSerializer<>(PostCountVO.class); - - JavaType top4OOTDVOListType = - objectMapper.getTypeFactory().constructCollectionType(List.class, Top4OOTDVO.class); - Jackson2JsonRedisSerializer> top4OOTDVOJackson2JsonRedisSerializer = - new Jackson2JsonRedisSerializer<>(top4OOTDVOListType); - - postCountVOJackson2JsonRedisSerializer.setObjectMapper(objectMapper); - top4OOTDVOJackson2JsonRedisSerializer.setObjectMapper(objectMapper); - + public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() { return (builder -> builder .withCacheConfiguration( @@ -72,7 +58,7 @@ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer( new StringRedisSerializer())) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( - new SnappyRedisSerializer(new KryoRedisSerializer<>())))) + new SnappyRedisSerializer(new KryoRedisSerializer<>())))) .withCacheConfiguration( "top4OOTD", RedisCacheConfiguration.defaultCacheConfig() @@ -83,6 +69,7 @@ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer( new StringRedisSerializer())) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( - new SnappyRedisSerializer>(new KryoRedisSerializer<>()))))); + new SnappyRedisSerializer>( + new KryoRedisSerializer<>()))))); } } diff --git a/src/main/java/com/dailyon/snsservice/config/RedisConfig.java b/src/main/java/com/dailyon/snsservice/config/RedisConfig.java index 62888d7..394610b 100644 --- a/src/main/java/com/dailyon/snsservice/config/RedisConfig.java +++ b/src/main/java/com/dailyon/snsservice/config/RedisConfig.java @@ -3,6 +3,9 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; + +import dailyon.domain.utils.KryoRedisSerializer; +import dailyon.domain.utils.SnappyRedisSerializer; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -55,10 +58,10 @@ private Set parseRedisNodes(String nodes) { } @Bean - public RedisTemplate redisTemplate() { - RedisTemplate redisTemplate = new RedisTemplate<>(); + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new SnappyRedisSerializer(new KryoRedisSerializer<>())); boolean isCluster = Objects.nonNull(env.getProperty("spring.redis.cluster.nodes")); if (!isCluster) { diff --git a/src/main/java/com/dailyon/snsservice/scheduler/PostScheduler.java b/src/main/java/com/dailyon/snsservice/scheduler/PostScheduler.java index ae602d2..dbaf2df 100644 --- a/src/main/java/com/dailyon/snsservice/scheduler/PostScheduler.java +++ b/src/main/java/com/dailyon/snsservice/scheduler/PostScheduler.java @@ -22,7 +22,7 @@ public class PostScheduler { @Scheduled(cron = "0 5 * * * *", zone = "Asia/Seoul") public void postCountCacheSyncToDB() { List> postCountVOStore = - postCountRedisRepository.findPostCountVOs("postCount"); + postCountRedisRepository.findPostCountVOs(); if (postCountVOStore != null) { postCountVOStore.forEach( store -> diff --git a/src/test/java/com/dailyon/snsservice/repository/post/PostCountRedisRepositoryTest.java b/src/test/java/com/dailyon/snsservice/repository/post/PostCountRedisRepositoryTest.java index 6ae8b9a..edfdee2 100644 --- a/src/test/java/com/dailyon/snsservice/repository/post/PostCountRedisRepositoryTest.java +++ b/src/test/java/com/dailyon/snsservice/repository/post/PostCountRedisRepositoryTest.java @@ -27,7 +27,7 @@ class PostCountRedisRepositoryTest { @Autowired private PostCountRedisRepository postCountRedisRepository; @Autowired private PostRepository postRepository; - @Autowired private RedisTemplate redisTemplate; + @Autowired private RedisTemplate redisTemplate; @Test @DisplayName("캐시 miss시 DB에서 캐시로 동기화 후 반환, 캐시 hit시 조회수, 좋아요수, 댓글수를 조회") @@ -90,7 +90,7 @@ void findPostCountVOs() { // when List> postCountVOStore = - postCountRedisRepository.findPostCountVOs(cacheName); + postCountRedisRepository.findPostCountVOs(); // then assertThat(postCountVOStore.size()).isNotSameAs(0); @@ -109,9 +109,8 @@ void findPostCountVOs() { void deletePostCountVO() throws JsonProcessingException { // given Long postId = 1L; - ObjectMapper objectMapper = new ObjectMapper(); - String stringValue = objectMapper.writeValueAsString(new PostCountVO(1, 2, 3)); - redisTemplate.opsForValue().set(String.format("postCount::%s", postId), stringValue); + PostCountVO postCountVO = new PostCountVO(1, 2, 3); + redisTemplate.opsForValue().set(String.format("postCount::%s", postId), postCountVO); // when postCountRedisRepository.deletePostCountVO(String.valueOf(postId)); diff --git a/src/test/java/com/dailyon/snsservice/service/CommentServiceTest.java b/src/test/java/com/dailyon/snsservice/service/CommentServiceTest.java index d2ea1ef..2ac303c 100644 --- a/src/test/java/com/dailyon/snsservice/service/CommentServiceTest.java +++ b/src/test/java/com/dailyon/snsservice/service/CommentServiceTest.java @@ -30,8 +30,7 @@ class CommentServiceTest { @Autowired private CommentService commentService; @Autowired private CommentReader commentReader; - @Autowired private RedisTemplate redisTemplate; - @Autowired private ObjectMapper objectMapper; + @Autowired private RedisTemplate redisTemplate; @Test @DisplayName("댓글 등록") @@ -47,10 +46,7 @@ void createComment() throws JsonProcessingException { Comment savedComment = commentService.createComment(memberId, postId, createCommentRequest); // then - PostCountVO afterPostCountVO = - objectMapper.readValue( - redisTemplate.opsForValue().get(String.format("postCount::%s", postId)), - PostCountVO.class); + PostCountVO afterPostCountVO = redisTemplate.opsForValue().get(String.format("postCount::%s", postId)); assertThat(savedComment.getDescription()).isEqualTo(commentDescription); assertThat(savedComment.getPost().getId()).isEqualTo(postId); diff --git a/src/test/java/com/dailyon/snsservice/service/PostLikeServiceTest.java b/src/test/java/com/dailyon/snsservice/service/PostLikeServiceTest.java index 3c4241a..dc13d36 100644 --- a/src/test/java/com/dailyon/snsservice/service/PostLikeServiceTest.java +++ b/src/test/java/com/dailyon/snsservice/service/PostLikeServiceTest.java @@ -33,7 +33,7 @@ class PostLikeServiceTest { @Autowired private PostLikeJpaRepository postLikeJpaRepository; - @Autowired private RedisTemplate redisTemplate; + @Autowired private RedisTemplate redisTemplate; @Autowired private ObjectMapper objectMapper; @@ -48,16 +48,7 @@ void createPostLike() throws JsonProcessingException { List beforePostCountVOs = postIds.stream() - .map( - postId -> { - try { - return objectMapper.readValue( - redisTemplate.opsForValue().get(String.format("postCount::%s", postId)), - PostCountVO.class); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - }) + .map(postId -> redisTemplate.opsForValue().get(String.format("postCount::%s", postId))) .collect(Collectors.toList()); // when @@ -68,42 +59,34 @@ void createPostLike() throws JsonProcessingException { // then postIds.forEach( postId -> { - try { - Optional postLike = - postLikeJpaRepository.findById(new PostLikeId(memberId, postId)); - if (postId.equals(2L)) { - assertThat(postLike.orElse(null)).isNull(); - PostCountVO afterPostCountVO = - objectMapper.readValue( - redisTemplate.opsForValue().get(String.format("postCount::%s", postId)), - PostCountVO.class); + Optional postLike = + postLikeJpaRepository.findById(new PostLikeId(memberId, postId)); + if (postId.equals(2L)) { + assertThat(postLike.orElse(null)).isNull(); + PostCountVO afterPostCountVO = + redisTemplate.opsForValue().get(String.format("postCount::%s", postId)); - assertThat( - beforePostCountVOs.stream() - .anyMatch( - beforePostCountVO -> - beforePostCountVO - .getLikeCount() - .equals(afterPostCountVO.getLikeCount() + 1))) - .isTrue(); - } else { - assertThat(postLike.orElse(null)).isNotNull(); - PostCountVO afterPostCountVO = - objectMapper.readValue( - redisTemplate.opsForValue().get(String.format("postCount::%s", postId)), - PostCountVO.class); + assertThat( + beforePostCountVOs.stream() + .anyMatch( + beforePostCountVO -> + beforePostCountVO + .getLikeCount() + .equals(afterPostCountVO.getLikeCount() + 1))) + .isTrue(); + } else { + assertThat(postLike.orElse(null)).isNotNull(); + PostCountVO afterPostCountVO = + redisTemplate.opsForValue().get(String.format("postCount::%s", postId)); - assertThat( - beforePostCountVOs.stream() - .anyMatch( - beforePostCountVO -> - beforePostCountVO - .getLikeCount() - .equals(afterPostCountVO.getLikeCount() - 1))) - .isTrue(); - } - } catch (JsonProcessingException e) { - throw new RuntimeException(e); + assertThat( + beforePostCountVOs.stream() + .anyMatch( + beforePostCountVO -> + beforePostCountVO + .getLikeCount() + .equals(afterPostCountVO.getLikeCount() - 1))) + .isTrue(); } }); } diff --git a/src/test/java/com/dailyon/snsservice/service/PostServiceTest.java b/src/test/java/com/dailyon/snsservice/service/PostServiceTest.java index 8d89225..67aca84 100644 --- a/src/test/java/com/dailyon/snsservice/service/PostServiceTest.java +++ b/src/test/java/com/dailyon/snsservice/service/PostServiceTest.java @@ -46,8 +46,7 @@ class PostServiceTest { @Autowired private PostService postService; @PersistenceContext private EntityManager em; - @Autowired private RedisTemplate redisTemplate; - @Autowired private ObjectMapper objectMapper; + @Autowired private RedisTemplate redisTemplate; @MockBean private ProductServiceClient productServiceClient; @MockBean private PromotionServiceClient promotionServiceClient; @@ -149,10 +148,10 @@ void softDeletePost() { postService.softDeletePost(postId, memberId); // then - String postCountVOStringValue = + PostCountVO postCountVO = redisTemplate.opsForValue().get(String.format("postCount::%s", postId)); - assertThat(postCountVOStringValue).isNull(); + assertThat(postCountVO).isNull(); assertThrowsExactly( NoResultException.class, () -> @@ -172,10 +171,7 @@ void addViewCount() throws JsonProcessingException { postService.addViewCount(postId); // then - PostCountVO postCountVO = - objectMapper.readValue( - redisTemplate.opsForValue().get(String.format("postCount::%s", postId)), - PostCountVO.class); + PostCountVO postCountVO = redisTemplate.opsForValue().get(String.format("postCount::%s", postId)); assertThat(postCountVO.getViewCount()).isSameAs(101); }