현재 서버에 실행 중인 프로젝트는 실제로 서비스 하지는 않지만 새로 배운 기술이나 어제 보다 더 나은 코드로 계속 리팩토링하고 있습니다. 가장 하고 싶었던 것 중 하나가 DB 입출력에 관한 것이었고 트래픽이 몰렸을 경우 병목 현상이 발생할 수 있는 지점을 캐싱하기 위해 Redis를 도입했습니다.
구현 코드의 주요 부분입니다.
@Configuration
public class RedisConfig {
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.host}")
private String host;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setHostName(host);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisCacheManager redisCacheManager() {
PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL);
GenericJackson2JsonRedisSerializer redisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
.defaultCacheConfig()
.serializeKeysWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(new StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(redisSerializer)
);
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory())
.cacheDefaults(redisCacheConfiguration)
.build();
}
}
@Cacheable(value = "User", key = "#email", cacheManager = "redisCacheManager")
public User getUserByEmail(final String email) {
log.info("getUserByEmail 메소드 호출");
return userRepository.findUserByEmail(email);
}
문제 상황
- User 객체의 LocalDateTime 타입의 데이터를 JSON 형식으로 직렬화하지 못해 Redis 저장에 오류가 발생했습니다.
해결 방법
- User의 createdAt 필드에 @JsonFormat을 String 타입으로 정하고 RedisCacheConfiguration 의 serializeValuesWith 속성에 JavaTimeModule을 추가한 ObjectMapper를 커스텀 하여 매핑했습니다.
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime createdAt;
@GetMapping("/get/info")
public UserInfoResponse readUser(Principal principal){
User user = userReadService.getUserByEmail(principal.getName());
return userReadService.findUserInfo(user);
}
상품 조회, 장바구니, 좋아요, ... 등등 여러 가지 기능을 사용할 때마다 User 정보 조회를 위한 select 쿼리가 계속해서 DB로 날라가고 이러한 DB 입출력은 모두 코스트라고 생각합니다. 실제로 서비스가 이루어지지 않기에 정확한 성능 변화를 알긴 어렵지만 단순하게 한 사용자가 서비스 기능을 10번 사용했을 때 9번의 select 쿼리를 줄였다고 생각한다면 단순 캐싱 기능만을 사용했지만 Redis는 굉장히 강력하다고 생각합니다.