
Write-Back은 우선 캐시 메모리에만 데이터를 Write 하여 사용하다가 캐시 메모리가 새로운 데이터 블록으로 교체되는 때에 (다른 Tag를 가진 데이터가 캐시 블록에 할당될 때) 데이터를 주기억장치에도 저장하는 정책
캐시 메모리에 있는 데이터를 여러 번 Overwrite하여 사용한 다음, 캐시 메모리가 해제되는 때에 메인 메모리에 데이터를 업데이트하는 정책
Write-Through 보다 훨씬 처리 속도가 빠름빠른 서비스를 요구하는 상황에는 Write Back을 사용하는 것이 좋음.
영화 조회 및 리뷰 서비스(https://github.com/chanmin-00/SpringProject_WatchWithMe)에서 리뷰를 작성하고 각 영화 리뷰 평점을 Redis 캐시에 저장한 후 스케줄링을 통하여 5분마다 Redis 캐시를 조회한 후 영화의 평균 평점 계산하여 데이터베이스에 저장하기
Write Back 전략을 사용하여 구현

@Repository
@RequiredArgsConstructor
public class ReviewCacheRepositoryImpl implements ReviewCacheRepository{
private final RedisTemplate<String, String> redisTemplate;
private static final String REVIEW_KEY_PREFIX = "reviews : ";
private static final String CHANGED_MOVIES_KEY = "changed_movies";
@Override
public void saveRating(String movieId, String value) {
redisTemplate.opsForList().rightPush(REVIEW_KEY_PREFIX + movieId, value);
}
@Override
public void saveChangedMovie(String movieId) {
redisTemplate.opsForSet().add(CHANGED_MOVIES_KEY, movieId);
}
@Override
public List<String> getRatingList(String movieId) {
return redisTemplate.opsForList().range(REVIEW_KEY_PREFIX + movieId, 0, -1);
}
@Override
public Set<String> getChangedMovieList() {
return redisTemplate.opsForSet().members(CHANGED_MOVIES_KEY);
}
@Override
public void deleteRatingList(String movieId) {
redisTemplate.delete(REVIEW_KEY_PREFIX + movieId);
}
@Override
public void deleteChangedMovie() {
redisTemplate.delete(CHANGED_MOVIES_KEY);
}
}
@Service
@RequiredArgsConstructor
@Transactional
public class ReviewCacheServiceImpl implements ReviewCacheService{
private final ReviewCacheRepository reviewCacheRepository;
@Override
@Transactional
public void saveRating(String movieId, String value) {
reviewCacheRepository.saveRating(movieId, value);
reviewCacheRepository.saveChangedMovie(movieId);
}
@Override
public List<String> getRatingList(String movieId) {
return reviewCacheRepository.getRatingList(movieId);
}
@Override
public Set<String> getChangedMovieList() {
return reviewCacheRepository.getChangedMovieList();
}
@Override
@Transactional
public void deleteRatingList(String movieId) {
reviewCacheRepository.deleteRatingList(movieId);
}
@Override
@Transactional
public void deleteChangedMovie() {
reviewCacheRepository.deleteChangedMovie();
}
}
public Long write(WriteReviewRequestDto writeReviewRequestDto){
String email = writeReviewRequestDto.email();
String reviewText = writeReviewRequestDto.reviewText();
Double memberRating = writeReviewRequestDto.memberRating();
String memberRatingGenre = writeReviewRequestDto.memberRatingGenre();
Long movieId = writeReviewRequestDto.movieId();
Member member = memberRepository.findByEmail(email).orElse(null);
if (member == null)
throw new GlobalException(GlobalErrorCode._BAD_REQUEST);
Movie movie = movieRepository.findById(movieId).orElse(null);
if (movie == null)
throw new GlobalException(GlobalErrorCode._BAD_REQUEST);
Review review = Review.createReview(reviewText, memberRating, memberRatingGenre);
review.setMember(member);
review.setMovie(movie);
reviewRepository.save(review);
reviewCacheService.saveRating(movieId.toString(), memberRating.toString());
return review.getReviewId();
}
reviewCacheService.saveRating(movieId.toString(), memberRating.toString());
saveRating 메소드를 호출하여 Redis에서 평점 저장@Scheduled(initialDelay=1000, fixedDelay = 60000 *3) // 3분마다 실행
@Transactional
public void calculateAverageReviewRating() {
Set<String> changedMovieList = reviewCacheService.getChangedMovieList();
if (changedMovieList != null) {
for (String movieIdStr : changedMovieList) {
Long movieId = Long.parseLong(movieIdStr);
List<String> ratingList = reviewCacheService.getRatingList(movieIdStr);
if (ratingList != null && !ratingList.isEmpty()){
Movie movie = movieService.getMovie(movieId);
double currentRating = (movie.getUserRating() != null) ? movie.getUserRating() : 0.0;
int reviewCount = movieService.getReviewList(movieId).size();
double newSum = ratingList.stream()
.mapToDouble(Double::parseDouble)
.sum() + currentRating * (reviewCount - ratingList.size());
movie.setUserRating(newSum / reviewCount);
reviewCacheService.deleteRatingList(movieIdStr);
}
}
reviewCacheService.deleteChangedMovie();
}
log.info("평점 업데이트 완료");
}