Redis + 스프링부트 연동하기 (feat. RDS)

Hannana·2024년 11월 26일
post-thumbnail

오오래 걸리는 크롤링에 캐싱 기능을 추가해보자.

Redis 설치하기

노트북이 윈도우 환경이므로
wsl환경에서 진행한다.

sudo apt install redis-server
 $ redis-cli ping
PONG

ping보내기

oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1943:C 21 Nov 2024 05:27:16.443 * Redis version=7.4.1, bits=64, commit=00000000, modified=0, pid=1943, just started
1943:C 21 Nov 2024 05:27:16.443 * Configuration loaded
1943:M 21 Nov 2024 05:27:16.444 * monotonic clock: POSIX clock_gettime
1943:M 21 Nov 2024 05:27:16.445 * Running mode=standalone, port=6379.
1943:M 21 Nov 2024 05:27:16.446 * Server initialized
1943:M 21 Nov 2024 05:27:16.446 * Ready to accept connections tcp

포트 충돌 없으면 잘 실행된다.

스프링부트에 적용해보기

spring:
  (생략)
  cache:
    type: redis
    data:
      redis:
        host: localhost
        port: 6379
        password: ""
        database: 0
        timeout: 2000ms
        lettuce:
          pool:
            max-size: 8
            max-idle: 8
            min-idle: 0
          shutdown-timeout: 100

application.yml 설정하기

6379 포트로 redis 열어주자.
패스워드는 별도로 설정하지 않았다.


@Configuration
@EnableCaching  // 캐시 기능 활성화
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // Key와 Value의 직렬화 방법 설정
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        return redisTemplate;
    }
    // RedisCacheManager 설정
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))  // 기본 TTL 설정 (10분)
                .disableCachingNullValues()  // Null 값을 캐시하지 않음
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))  // 캐시 키 직렬화
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));  // 캐시 값 직렬화

        // RedisCacheManager 설정
        RedisCacheManager.RedisCacheManagerBuilder cacheManagerBuilder = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfig);  // 기본 캐시 설정 적용

        // 특정 캐시 이름에 대해 TTL을 다르게 설정 (예: "userCache"는 30분 TTL)
        cacheManagerBuilder.withCacheConfiguration("userCache",
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofMinutes(30))  // userCache는 30분 TTL
                        .disableCachingNullValues()
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())));

        return cacheManagerBuilder.build();
    }
}

RedisConfig 설정하기

@Slf4j
@Service
@RequiredArgsConstructor
@EnableCaching
public class EduContentsService {
    @Value("${youtube.playlist-url}")
    private String playlistUrl;
    @Cacheable(value = "crawledVideos", key = "'playlist_' + #playlistUrl")
    public List<EduContentsDto> fetchYoutubeVideos() {
        log.info("Fetching YouTube videos for playlist: {}", playlistUrl);
        WebDriverManager.chromedriver().browserVersion("131.0").setup();

캐싱을 원하는 코드에 @EnableCaching 어노테이션 달아주기

이렇게 크롤링한 데이터가 캐싱된 것을 확인 할 수 있다.

고도화 적용

단순히 캐싱을 해도 좋지만,
RDS와 연동해서 RDS에 캐싱 데이터를 쌓아두기로!
서버가 연결이 끊기더라도 캐싱 데이터가 저장,복구가 가능해진다.

흐름은
RDS에 캐시 데이터 조회 -> (없을 시) 캐싱하고 쌓기

  datasource:
    url: jdbc:mysql://{RDS endpoint}:3306/moonhyoman
    username: root
    password: {PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      pool-name: HikariCP
      maximum-pool-size: 10
      minimum-idle: 5
      idle-timeout: 30000
      max-lifetime: 1800000
      connection-timeout: 20000
      connection-test-query: SELECT 1
      leak-detection-threshold: 3000

yml 환경 설정

RDS repository만들어주기

Redis에서 확인 후 없으면 RDS 저장 및 Redis 캐싱

//크롤링 데이터를 dto에 매핑
EduCardDto eduCardDto = EduCardDto.builder()
                            .eduType("cardnews")
                            .category("금융")
                            .titles(title)
                            .link(link)
                            .date(date)
                            .build();

//Redis에서 데이터 확인 후 없으면 RDS 저장 및 Redis 캐싱
                    String cacheKey = "cardnews_" + eduCardDto.getLink();
                    if (!redisTemplate.hasKey(cacheKey)) {
                        if (!eduCardRepository.existsByLink(eduCardDto.getLink())) {
                            // 6.1 RDS에 데이터 저장
                            EduCard eduCard = EduCard.builder()
                                    .eduType(eduCardDto.getEduType())
                                    .category(eduCardDto.getCategory())
                                    .titles(eduCardDto.getTitles())
                                    .link(eduCardDto.getLink())
                                    .date(eduCardDto.getDate())
                                    .build();
                            eduCardRepository.save(eduCard);
                            log.info("Saved new EduCard to repository: {}", eduCard.getLink());
                        }

  //Redis에 캐싱 (10분 TTL 설정)
 redisTemplate.opsForValue().set(cacheKey, eduCardDto, 10, TimeUnit.MINUTES);
log.info("Cached new EduCard data to Redis: {}", eduCardDto.getLink());

api호출 시, RDS에 저장된 데이터를 Redis로 로드(RedisTemplate)

 @PostConstruct
    public void loadCacheFromRds() {
        List<EduCard> eduCards = eduCardRepository.findAll();
        for (EduCard eduCard : eduCards) {
            String cacheKey = "cardnews_" + eduCard.getLink();
            redisTemplate.opsForValue().set(cacheKey, eduCard);
        }
        log.info("Loaded all EduCard data from RDS into Redis cache");
    }

RDS 적용 후

테이블에 크롤링 데이터가 저장 된 모습


성능 체크

@Cacheable 가 적용되면 동일한 요청이 들어올 경우 결과를 캐시에서 반환해서 실제 메서드가 실행되지 않아 네트워크 요청 시간을 다시 측정할 수 없었다.
그래서 별도의 테스트 코드를 작성한 후 적용 시, 미적용 시 각각 테스트했다.

최종

  • 캐싱 적용 전

    1분 49초 - 8배속 재생
  • 캐싱 적용 후

    2초 - 1배속 재생
  • 실행 시간 체크

    시간이 기하급수적으로 줄어든 것을 볼 수 있다.
    체감 상 캐싱이 적용되면 delay없이 바로 나온다.
profile
(구) https://hansjour.tistory.com/ 이사옴. 성장하는 하루를 쌓아가는 블로그

0개의 댓글