[Shopping Mall] Redis

이정민·2023년 12월 14일
0

쇼핑몰 프로젝트

목록 보기
4/5
post-thumbnail
post-custom-banner

현재 서버에 실행 중인 프로젝트는 실제로 서비스 하지는 않지만 새로 배운 기술이나 어제 보다 더 나은 코드로 계속 리팩토링하고 있습니다. 가장 하고 싶었던 것 중 하나가 DB 입출력에 관한 것이었고 트래픽이 몰렸을 경우 병목 현상이 발생할 수 있는 지점을 캐싱하기 위해 Redis를 도입했습니다.


Redis 설치(EC2 서버)

  • AWS EC2 서버에 직접 설치하여 사용했습니다.


병목 지점 파악

  • 현재 서비스는 Spring Security를 사용하고 있어 인증이 필요한 곳은 Principal 객체를 통해 인증된 email로 User 객체를 찾아오고 있습니다.
    그래서 인증이 필요한 모든 엔드포인트에 메소드가 사용되고 있고 이는 트래픽이 몰리게 된다면 충분히 병목 현상으로 이어질 수 있다고 생각했습니다.


구현 코드의 주요 부분입니다.

RedisConfig.java

  • 방식은 같으나 security filter에서 발생하는 예외인 AuthenticationException을 상속 받아 구현
@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();
    }
}

UserReadService.getUserByEmail()

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

실행 예시

  • 아래의 api를 한 번 실행하면 getUserByEmail() 메소드의 결과가 캐싱되고 두 번째 실행했을 때 redis에 캐싱된 데이터가 있어 findUserInfo() 메소드만 호출된 것을 볼 수 있습니다.
    @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는 굉장히 강력하다고 생각합니다.


전체 코드

https://github.com/SudalKing/Shopping_mall/tree/main

post-custom-banner

0개의 댓글