이전에 이어 페이지네이션 관련 로직을 최적화해보도록 하겠습니다.
https://soyeonnnb.tistory.com/124
먼저, 쿼리 DSL을 쓰지 않은 결과부터 보도록 하겠습니다.
쿼리DSL 안쓴거
다음과 같이 단순히 jpa와 pageable 객체를 이용해 데이터를 가져와주었습니다.
List<Feed> feedList = feedRepository.findByIdLessThanAndWriterInOrderByIdDesc(cursorId,
searchUserList, PageRequest.of(0, 20));
그러자, 시간이 매우 많이 걸리는 것을 확인할 수 있습니다.
쿼리 DSL쓴거
그래서, 이번에는 쿼리DSL를 써보도록 하겠습니다.
List<Feed> feedList = feedRepository.findByIdLessThanAndWriterInOrderByIdDescQuerydsl(cursorId,
searchUserList);
다음과 같이 필요한 부분에 대해 join을 걸고, limit을 주었습니다.
@Override
public List<Feed> findByIdLessThanAndWriterInOrderByIdDescQuerydsl(Long cursorId, List<User> userList) {
List<Feed> results = queryFactory
.selectFrom(feed)
.leftJoin(feed.feedImageList).fetchJoin()
.leftJoin(feed.store, store).fetchJoin()
.leftJoin(store.emdAddress, emdAddress).fetchJoin()
.leftJoin(emdAddress.siggAddress, siggAddress).fetchJoin()
.leftJoin(siggAddress.sidoAddress, sidoAddress).fetchJoin()
.where(feed.id.lt(cursorId)
.and(feed.writer.in(userList)))
.orderBy(feed.id.desc())
.limit(20)
.fetch();
return results;
}
하지만, 속도가 전혀 개선되지 않았음을 확인할 수 있었습니다.
개선하기
이게 원인이 아닐 거라 생각이 들어 쿼리를 확인해보았습니다.
그러자 limit이 걸리지 않았음을 확인할 수 있었습니다. !!! 그래서 모든 데이터를(더미데이터 기준 10만개) 다 가져와야 했기 때문이라고 유추할 수 있었습니다.
Hibernate:
select
f1_0.id,
f1_0.content,
f1_0.created_at,
fil1_0.feed_id,
fil1_0.id,
fil1_0.created_at,
fil1_0.number,
fil1_0.updated_at,
fil1_0.url,
f1_0.store_id,
s1_0.id,
s1_1.description,
s1_1.joined_at,
s1_1.name,
s1_1.nickname,
s1_1.phone,
s1_1.profile_image,
s1_1.updated_at,
s1_0.address,
s1_0.detail_address,
s1_0.email,
s1_0.emd_id,
s1_0.latitude,
s1_0.longitude,
s1_0.password,
s1_0.store_name,
s1_0.store_phone,
f1_0.updated_at,
f1_0.user_id
from
feed f1_0
left join
feed_image fil1_0
on f1_0.id=fil1_0.feed_id
left join
(store s1_0
join
user s1_1
on s1_0.id=s1_1.id)
on s1_0.id=f1_0.store_id
where
f1_0.id<?
and f1_0.user_id in (?, ?, ?, ?)
order by
f1_0.id desc
알아보니, QueryDsl에서 fetch join과 limit 이 함께 사용이 안된다고 합니다....!
그 이유는 간단히 말해 limit절의 경우(즉, Pagination의 작업은) 쿼리에서 진행되는 것이 아닌, 메모리/애플리케이션 단에서 수행이 되기 때문입니다.
저의 경우는
1️⃣ limit이 걸린 데이터를 가져옵니다.
2️⃣ 해당 데이터에 대해서 fetch join하는 데이터를 가져옵니다.
이렇게 총 2개의 쿼리를 날려 해결하였습니다.
@RequiredArgsConstructor
public class FeedQuerydslRepositoryImpl implements FeedQuerydslRepository {
private final JPAQueryFactory queryFactory;
private final FeedImageRepository feedImageRepository;
private final FeedLikeRepository feedLikeRepository;
@Override
public List<FeedResponse> findByIdLessThanAndWriterInOrderByIdDescQuerydsl(Long cursorId, List<User> userList, User loginUser) {
List<Feed> feeds = queryFactory
.selectFrom(feed)
.where(feed.id.lt(cursorId)
.and(feed.writer.in(userList)))
.orderBy(feed.id.desc())
.limit(20)
.fetch();
// 가져온 feed ID 목록 추출
List<Long> feedIds = feeds.stream()
.map(Feed::getId)
.collect(Collectors.toList());
// 두 번째 쿼리: feed ID 목록을 이용해 연관 데이터 가져오기 (fetch join 사용 가능)
List<Feed> results = queryFactory
.selectFrom(feed)
.leftJoin(feed.feedImageList).fetchJoin()
.leftJoin(feed.store, store).fetchJoin()
.where(feed.id.in(feedIds))
.fetch();
return results.stream()
.map(f -> {
List<FeedImage> images = feedImageRepository.findAllByFeed(f);
boolean isLike = feedLikeRepository.existsByFeedAndUser(f, loginUser);
return FeedResponse.of(f, images, isLike);
})
.collect(Collectors.toList());
}
}
속도가 드디어 개선되었습니다 !!!
쿼리 또한, 2 + 20(좋아요 여부)개가 날라가는 것을 확인할 수 있었습니다.
관련 코드는 다음 깃허브에서 확인해주세요.
쿼리 DSL 설정부터 해당 로직까지의 커밋입니다
https://github.com/soyeonnnb/restagram-api/commit/f68fb5569c9446e8cb526675f0ae370e273d0f78
'프로젝트' 카테고리의 다른 글
[트러블슈팅] 객체 삭제 시 org.hibernate.StaleObjectStateException 에러 발생 (0) | 2024.06.24 |
---|---|
[QueryDSL] 피드의 각 첫번째 이미지 가져오는 쿼리 개선하기 (0) | 2024.06.13 |
[Java] Record란? & 프로젝트에 Record 적용해보기 (0) | 2024.06.12 |
[Restagram] 리팩토링 (0) | 2024.06.11 |
[Spring] 오프셋 기반 vs 커서 기반 페이지네이션 (0) | 2024.06.06 |