[Redis] 게시물 조회 캐싱하기(Pagination)

Nicky·2024년 3월 14일
post-thumbnail

처음 A1BnB 홈페이지에 접근하면 다음과 같이 호스트가 등록한 게시물들을 확인할 수 있다. 현재 홈페이지에서 페이지당 8개씩의 게시물들을 최신순으로 노출시키고 있다.

게시물 조회 클래스

게시물 조회 관련 클래스들은 다음과 같다.

PostResponse (DTO)

// 게시물 응답
public record PostResponse(Long postId, String authorName, List<String> photoUrls, String location,
                           Double pricePerNight, Integer likeCount) {
    public PostResponse {
    }
}

PostResponseMapper (Mapper)

@Component
public class PostResponseMapper {

    public List<PostResponse> toPostResponses(List<Post> posts) {
        return posts.stream()
                .map(post -> toPostResponse(post))
                .collect(Collectors.toList());
    }

    public PostResponse toPostResponse(Post post) {
        return new PostResponse(
                post.getPostId(),
                post.getAuthor().getName(),
                getPhotoUrls(post),
                post.getLocation(),
                post.getPricePerNight(),
                post.getLikeCount()
        );
    }

    private List<String> getPhotoUrls(Post post) {
        return post.getPhotos().stream()
                .map(Photo::getOriginalUrl)
                .collect(Collectors.toList());
    }

}

getAllPosts (Service 메서드)

    // 게시물 응답 DTO Page 반환
    @Override
    @Transactional(readOnly = true)
    public Page<PostResponse> getAllPosts(Pageable pageable) {
        Page<Post> postPage = postRepository.findAll(pageable);
        // 게시물 응답 DTO 반환
        return postPage.map(postResponseMapper::toPostResponse);
    }

게시물 조회시 각 Post 엔티티의 필드들에 접근해야하기 때문에, 이용자들이 홈페이지에 접근할 때마다 MySQL 데이터베이스에 조회 쿼리를 날려야하므로 비효율적이다.

지난 포스팅에서 연결한 ElastiCashe Redis 캐시를 이용해 게시판 조회 성능을 올려보자!

Caching 적용하기

@EnableCaching 적용

우선 Application 클래스에 @EnableCaching 어노테이션을 적용해야 한다.

@EnableCaching
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

@Cacheable

메서드에 캐싱을 적용하려면 우선 @Cacheable에 대해 알아보자.
@Cacheable 어노테이션은 메서드의 실행 결과를 캐싱할 때 사용되며, 다양한 파라미터를 통해 캐싱 동작 방식을 제어할 수 있다.

  • cacheNames: 캐시 이름 지정, 여러 캐시 지정 가능
  • key: 캐시에 저장될 데이터의 키 지정
  • condition: 캐싱을 적용할 조건
  • unless: 메서드의 반환값을 검사하여 조건이 true일 경우 결과를 캐시 추가 X,
    condition이 메서드 호출 전에 평가되는 반면, unless는 메서드 호출 후에 평가
  • keyGenerator: key 파라미터 대신 사용, 캐시 키를 생성하기 위한 커스텀 KeyGenerator를 지정
  • sync: 여러 스레드가 동시에 같은 키로 캐시되지 않은 메서드를 호출하면, sync가 true로 설정되어 있을 때 하나의 스레드만 메서드를 실행하고 나머지 스레드는 결과를 공유받음. 동시성 문제를 방지

CustomKeyGenerator

앞서 설명한 KeyGenerator를 이용해 Page의 number로 키를 지정해보자.

@Component("cachingKeyGenerator")
public class CachingKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        Pageable pageable = (Pageable) params[0];
        return "Page:" + pageable.getPageNumber();
    }
}

getAllPosts

    // 게시물 응답 DTO Page 반환
    @Override
    @Transactional(readOnly = true)
    @Cacheable(cacheNames = "findPosts", keyGenerator = "cachingKeyGenerator")
    public Page<PostResponse> getAllPosts(Pageable pageable) {
        Page<Post> postPage = postRepository.findAll(pageable);
        // 게시물 응답 DTO 반환
        return postPage.map(postResponseMapper::toPostResponse);
    }

DTO 클래스

데이터를 캐싱하려면 저장하는 객체를 직렬화해야 한다.

// 게시물 응답
public record PostResponse(Long postId, String authorName, List<String> photoUrls, String location,
                           Double pricePerNight, Integer likeCount) implements Serializable {
    public PostResponse {
    }
}

게시물 조회 테스트

테스트를 위해 우선 redis-cli를 준비하자.
현재 redis에는 어떤한 데이터도 저장되어 있지 않은 상태이다.


홈페이지 접근이후 getAllPosts 메서드를 통해 데이터가 저장된것을 확인할 수 있다.

캐시 무효화(@CacheEvict)

만약 기존의 게시물 리스트에서 게시물의 추가나 삭제가 행해진다면.. 페이지의 정보가 변하기 때문에 redis에서 관리하는 데이터들이 더이상 쓸모가 없어진다.

캐시 무효화는 캐싱된 데이터를 최신 상태로 유지하기 위해, 더 이상 유효하지 않거나 오래된 데이터를 캐시에서 제거하는 과정을 뜻한다.

이번에는 @CacheEvict를 통해 캐시 데이터를 삭제해보자.

registerPost

allEntries는 true시에 해당 캐시의 모든 항목을 제거해준다.

    // 게시물 등록
    @Override
    @Transactional
    @CacheEvict(cacheNames = "findPosts", allEntries = true)
    public void registerPost(String username, PostUploadRequest requestParam) {
        Member currentMember = memberService.findMember(username);
        List<Photo> photos = photoService.findPhotos(requestParam.photoIdList());
        List<Date> availableDates = dateService.getDates(requestParam.startDate(), requestParam.endDate());
        savePost(requestParam, currentMember, photos, availableDates);
    }

게시물 업로드 테스트

게시물 업로드로 확인해보자.

저장된 데이터가 삭제 된것을 확인할 수 있다.

profile
코딩 연구소

0개의 댓글