TIL [20240626] - Redis Cache

이윤성·2024년 6월 26일
0

TIL

목록 보기
44/51

오늘은 AWS 설정 준비 강의를 들은 것 이후로 실습반 과제를 만드는 중이었습니다.

지난 주 실습반 과제는 movie-personalize-api에서 movie-info-api를 호출하여 전체 영화 목록을 가져온 후, 이 목록에서 장르별로 영화를 필터링하는 API를 만들었습니다.
movie-info-api에서 가져오는 영화 데이터는 거의 변경 되지 않는 데이터입니다. 데이터 변경 가능성이 매우 낮은 경우, 캐시에 저장하면 성능 향상에 큰 효과를 볼 수 있습니다.

과제


문제점

실습에서 본 코드는 비효율적입니다. 장르별로 추출한 결과를 레디스에 저장하고 있기 때문입니다. 이 때 다른 장르의 요청이 들어온다면 만들어 놓은 캐시는 쓸모 없게 됩니다.

이 경우는 영화 전체 목록 API를 캐싱하고 뒤에 특정 장르를 추출하는 것이 훨씬 이득입니다.

요구사항

  • 영화 전체 목록 API 응답을 레디스에 저장할 수 있도록 변경하세요
  • 캐싱된 목록에서 요청한 장르를 추출할 수 있도록 수정하세요

과제는 위와 같은 방식으로 진행되었습니다. 먼저 getMoviesByGenre를 호출하여 잘 넘어오는 지 확인을 합니다.

확인을 해보니 계속 500 코드를 반환하는 것이 문제가 있어보였습니다. 과제를 위해 서버는 계속 돌고있을 거란 가정으로 제 인터넷이랑 설정이 문제인가 만져봤는데 결론이 나지않았습니다.

그래서 튜터님에게 도움을 요청했고 해결했습니다. 외부 API 전달 역할을 해줄 서버가 꺼져있던게 문제였습니다. 그래서 현업에 가서도 QA, Develop 같은 테스트 서버들이 있는데 사용을 안할 시에는 꺼두기에 이런 문제를 만나면 저한테서 문제를 찾기보다 해당 팀에 요청을 하는 것이 빠르다 이걸 배웠습니다.

코드를 작성하기 시작했습니다. 캐시 방법으로는 @Cacheable 어노테이션을 찾긴했는데 RedisConfig를 가보니 RedisTemplate<String, Object>가 빈으로 등록되어있는 것을 확인했습니다. 그래서 의존성 주입을 받은 후 작업을 진행했습니다.

Redis는 C#의 Dictionary라고 생각하면 편했습니다. key-value의 쌍을 저장하는 저장소이기 때문입니다. 먼저 아래와 같은 키값을 작성했습니다.

private static final String MOVIE_CACHE_KEY = "all_movies";

그리고 webClient에서 나온 값을 redisTemplate안에 저장해야되는데 이것이 좀 어려웠습니다.

return webClient.get()
			.uri(movieInfoApiUriComponent.toUriString())
			.retrieve()
			.bodyToFlux(MovieDto.class)
			.collectList()
			.doOnNext(movies -> {
				redisTemplate.opsForValue().set(MOVIE_CACHE_KEY, movies, Duration.ofMinutes(CACHE_TTL_MINUTES));
			})
			.flatMapMany(Flux::fromIterable)
            ...

bodyToFlux까지는 기존 코드와 같지만 CollectList로 모든 요소를 수집하여 하나의 List로 변환을 했습니다. 이 함수는 Flux를 Mono<List>로 변환합니다. doOnNext()는 문서를 봐도 설명하기엔 이해가 잘안됬습니다. reactor Mono 이 부분을 참고했습니다. 사람들이 쓴 글을 인용하자면 데이터가 존재할 때만 반환되는 듯 합니다. 성공적으로 진행되면 redisTemplate에 key-value값을 등록합니다. 완료가 되면 flatMapMany를 사용해 다시 flux로 변환합니다.

위 과정을 진행하니 Flux에 대한 이해와 리액트에도 좀 공부가 필요해보였습니다. 배워야 할 것이 계속 나와서 갈 길이 먼 것같습니다. 마지막 처리는 RedisTemplate의 값이 Object-Type이었으니 List<MovieDto>로 변경을 진행해야 합니다.

private List<MovieDto> getCachedMovies() {
	Object cachedValue = redisTemplate.opsForValue().get(MOVIE_CACHE_KEY);
	if(cachedValue instanceof List){
		return ((List<?>)cachedValue).stream()
			.filter(item -> item instanceof MovieDto)
			.map(item -> (MovieDto)item)
			.collect(Collectors.toList());
	}
	return null;
}
List<MovieDto> cachedMovies = getCachedMovies();
if (cachedMovies != null) {
	return Flux.fromIterable(cachedMovies);
}

캐시 된 내용이 있으면 반환을 해줍니다. 캐시 수명이 잘 작동하는지 확인하기 위해 30초 뒤에 캐시가 사라지도록 설정을 해두고 테스트해서 작동하는 것도 확인했습니다.

아래는 캐시가 없을 때와 있는 차이입니다. 당연히 캐시 된 것이 빠르지만 메모리에 저장되어서 더 빠른 건지 캐시를 해도 다른 비교군을 놔뒀으면 더 확실하게 데이터로 비교가 됐을 것같은데 이 부분에 대해서는 더 찾아보거나 질문을 해봐야겠습니다.

오늘은 이 정도로 마무리하고 내일은 AWS 마무리 및 JPA 심화 강의를 들을 것같습니다.

0개의 댓글