
오오래 걸리는 크롤링에 캐싱 기능을 추가해보자.
노트북이 윈도우 환경이므로
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");
}
테이블에 크롤링 데이터가 저장 된 모습

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


