@Query("select r from Review r join fetch r.member join fetch r.product")
Slice<Review> findPageBy(final Pageable pageable);
fetch join 을 통해 N+1 문제를 해결하고 성능테스트를 해본 결과 최근 리뷰 조회 시 평균 latency 가 4 초.
개선이 필요했다.
offset 을 사용하면 항상 첫 번째 row 부터 읽어온다.(그 뒤에 필요한 row 를 제외한 나머지는 버린다) 즉 데이터가 쌓일수록 마지막에 가까운 페이지를 요청할수록 읽어올 데이터가 늘어난다.
당연히 no-offset 이 가장 빠른 속도를 냈다.
페이징 구현 시 먼저 no-offset 사용을 고려해 본다. 만약 사용할 조건이 되지 않는다면 offset 방식(커버링 인덱스)으로 구현하도록 한다.
애초에 문제가 되는 offset 을 사용하지 않는 방법이다. 클라이언트에게 마지막으로 응답받은 row 번호를 받아 그 이후의 필요한 데이터에만 접근하는 것이다. 당연히 너무 좋은 방법이지만 두 가지 조건을 충족해야 사용할 수 있다.
최근 리뷰 조회의 경우 id 를 가지고 중복 없이 순서를 매길수 있고, 프론트엔드에서 무한 스크롤을 사용하기 때문에 offset 을 사용하지 않고 페이징 구현을 할 수 있다.
public List<Review> findPageBy(final Long reviewId, final int pageSize) {
return jpaQueryFactory.selectFrom(review)
.where(ltReviewId(reviewId))
.innerJoin(review.member, member)
.fetchJoin()
.innerJoin(review.product, product)
.fetchJoin()
.orderBy(review.id.desc())
.limit(pageSize)
.fetch();
}
private BooleanExpression ltReviewId(final Long reviewId) {
if (reviewId == null) {
return null;
}
return review.id.lt(reviewId);
}
offset 사용 시 불필요한 데이터 조회 비용을 최소화하기 위해서 먼저 첫 row 부터 요청한 row 까지의 리뷰를 id 만 조회하도록 한 뒤(PK 는 클러스터 인덱스로 자동 등록, 즉 커버링 인덱스 적용됨) 실제 필요한 row 의 데이터만 조회하도록 구현했다.
public Slice<Review> findByPage(final Pageable pageable) {
final JPAQuery<Long> coveringIndexQuery = jpaQueryFactory.select(review.id)
.from(review)
.offset(pageable.getOffset())
.limit(pageable.getPageSize() + 1)
.orderBy(makeOrderSpecifiers(review, pageable));
final Slice<Long> reviewIds = toSlice(pageable, coveringIndexQuery.fetch());
if (reviewIds.isEmpty()) {
return new SliceImpl<>(Collections.emptyList(), pageable, false);
}
final JPAQuery<Review> query = jpaQueryFactory.selectFrom(review)
.where(review.id.in(reviewIds.getContent()))
.innerJoin(review.member, member)
.fetchJoin()
.innerJoin(review.product, product)
.fetchJoin()
.orderBy(makeOrderSpecifiers(review, pageable));
return new SliceImpl<>(query.fetch(), pageable, reviewIds.hasNext());
}
참고
https://jojoldu.tistory.com/528
https://jojoldu.tistory.com/529