Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] add redis compression #59

Merged
merged 2 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.2'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final RedisTemplate<String, PostCountVO> 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")
Expand All @@ -40,27 +44,28 @@ public PostCountVO modifyPostCountVOAboutLikeCount(String key, PostCountVO postC
@CacheEvict(value = "postCount", key = "#key")
public void deletePostCountVO(String key) {}

public List<Map<String, PostCountVO>> findPostCountVOs(String cacheName) {
Set<String> 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<Map<String, PostCountVO>> findPostCountVOs() {
Set<String> allKeys = redisTemplate.keys("postCount::*");

// Fetch values for each key
List<Map<String, PostCountVO>> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,21 @@
public class Top4OOTDRedisRepository {

private final PostRepository postRepository;
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final RedisTemplate<String, List<Top4OOTDVO>> redisTemplate;

@Cacheable(value = "top4OOTD", key = "#productId", unless = "#result == null")
public List<Top4OOTDVO> findOrPutTop4OOTDVO(String productId) throws JsonProcessingException {
String stringValue = redisTemplate.opsForValue().get(productId);
if (isInValidValue(stringValue)) {
List<Top4OOTDVO> cachedTop4OOTDVOs = redisTemplate.opsForValue().get(productId);
if (isInValidValue(cachedTop4OOTDVOs)) {
List<Post> 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<Top4OOTDVO> value) {
return value == null;
}
}
24 changes: 7 additions & 17 deletions src/main/java/com/dailyon/snsservice/config/CacheConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

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;
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;
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;

Expand Down Expand Up @@ -44,19 +45,7 @@ public RedisCacheConfiguration redisCacheConfiguration(ObjectMapper objectMapper
}

@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(
ObjectMapper objectMapper) {
Jackson2JsonRedisSerializer<PostCountVO> postCountVOJackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(PostCountVO.class);

JavaType top4OOTDVOListType =
objectMapper.getTypeFactory().constructCollectionType(List.class, Top4OOTDVO.class);
Jackson2JsonRedisSerializer<List<Top4OOTDVO>> top4OOTDVOJackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(top4OOTDVOListType);

postCountVOJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
top4OOTDVOJackson2JsonRedisSerializer.setObjectMapper(objectMapper);

public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder ->
builder
.withCacheConfiguration(
Expand All @@ -69,7 +58,7 @@ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
postCountVOJackson2JsonRedisSerializer)))
new SnappyRedisSerializer<PostCountVO>(new KryoRedisSerializer<>()))))
.withCacheConfiguration(
"top4OOTD",
RedisCacheConfiguration.defaultCacheConfig()
Expand All @@ -80,6 +69,7 @@ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
top4OOTDVOJackson2JsonRedisSerializer))));
new SnappyRedisSerializer<List<Top4OOTDVO>>(
new KryoRedisSerializer<>())))));
}
}
9 changes: 6 additions & 3 deletions src/main/java/com/dailyon/snsservice/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,10 +58,10 @@ private Set<RedisNode> parseRedisNodes(String nodes) {
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
public <T> RedisTemplate<String, T> redisTemplate() {
RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new SnappyRedisSerializer<T>(new KryoRedisSerializer<>()));

boolean isCluster = Objects.nonNull(env.getProperty("spring.redis.cluster.nodes"));
if (!isCluster) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class PostScheduler {
@Scheduled(cron = "0 5 * * * *", zone = "Asia/Seoul")
public void postCountCacheSyncToDB() {
List<Map<String, PostCountVO>> postCountVOStore =
postCountRedisRepository.findPostCountVOs("postCount");
postCountRedisRepository.findPostCountVOs();
if (postCountVOStore != null) {
postCountVOStore.forEach(
store ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PostCountRedisRepositoryTest {

@Autowired private PostCountRedisRepository postCountRedisRepository;
@Autowired private PostRepository postRepository;
@Autowired private RedisTemplate<String, String> redisTemplate;
@Autowired private RedisTemplate<String, PostCountVO> redisTemplate;

@Test
@DisplayName("캐시 miss시 DB에서 캐시로 동기화 후 반환, 캐시 hit시 조회수, 좋아요수, 댓글수를 조회")
Expand Down Expand Up @@ -90,7 +90,7 @@ void findPostCountVOs() {

// when
List<Map<String, PostCountVO>> postCountVOStore =
postCountRedisRepository.findPostCountVOs(cacheName);
postCountRedisRepository.findPostCountVOs();

// then
assertThat(postCountVOStore.size()).isNotSameAs(0);
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ class CommentServiceTest {
@Autowired private CommentService commentService;

@Autowired private CommentReader commentReader;
@Autowired private RedisTemplate<String, String> redisTemplate;
@Autowired private ObjectMapper objectMapper;
@Autowired private RedisTemplate<String, PostCountVO> redisTemplate;

@Test
@DisplayName("댓글 등록")
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class PostLikeServiceTest {

@Autowired private PostLikeJpaRepository postLikeJpaRepository;

@Autowired private RedisTemplate<String, String> redisTemplate;
@Autowired private RedisTemplate<String, PostCountVO> redisTemplate;

@Autowired private ObjectMapper objectMapper;

Expand All @@ -48,16 +48,7 @@ void createPostLike() throws JsonProcessingException {

List<PostCountVO> 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
Expand All @@ -68,42 +59,34 @@ void createPostLike() throws JsonProcessingException {
// then
postIds.forEach(
postId -> {
try {
Optional<PostLike> 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> 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();
}
});
}
Expand Down
Loading
Loading