오프셋 페이징
offset
: 어디부터 시작해서 조회할건지?
limit
: 몇개를 조회할건가?
SELECT * FROM Reservation r ORDER BY r.id DESC OFFSET 페이지번호 LIMIT 페이지사이즈
- 해당 쿼리는 뒤로 갈수록 느려지는 데 이는 앞에서 읽었던 행을 다시 읽어야 하기 때문이다.
offset 10000, limit 10
이라 하면 최종적으로 10,010개의 행을 읽어야 하기 때문에 점점 느려질 수 밖에 없다.
No offset 방식
- 조회 시작 부분을 인덱스로 빠르게 찾아 매번 첫 페이지만 읽도록 하는 방식
SELECT * FROM Reservation r WHERE r.id < 마지막조회 ID ORDER BY r.id DESC LIMIT 페이지사이즈
- 매번 이전 페이지 전체를 건너뛸 수 있게 된다.
- 해당 방식으로 사용하면 클라이언트에서 스크롤 했을 때 마지막 예약 번호만 넘겨주면 무한스크롤 방식으로도 사용이 가능하다!
- 성능도 좋아지고 일석이조!
구현 코드
@Override public List<ReservationDto> findReservationsByGuestAndStatus( Long lastReservationId, int pageSize, User guest, ReservationStatus status ) { return queryFactory.select(toReservationDto(status)) .from(reservation) .innerJoin(reservation.room, room) .innerJoin(room.host, user) .where(ltReservationId(lastReservationId), eqStatus(status), eqGuest(guest)) .limit(pageSize) .orderBy(reservation.id.desc()) .fetch(); }
페이징 타입
- Page
- 데이터 총 개수, 다음 페이지의 존재 여부, 페이징해서 가져온 데이터 확인 가능
- 데이터의 총 개수를 가지고 있기 때문에 조회시 count쿼리를 한번 더 실행
- Slice
- 다음 페이지의 존재 여부와 페이징해서 가져온 데이터는 확인할 수 있으나 데이터의 총 개수에 대한 정보는 가지고 있지 않다.
- count 쿼리 대신 limit + 1조회를 통해 마지막 페이지인지 정보를 확인하기 때문에 page보다 조금의 성능 이점이 있다.
게시판처럼 전체 페이지의 수를 보여줄 필요가 있다면 Page를 무한 스크롤처럼 전체 페이지의 수를 알 필요가 없다면 Slice를 사용하면 된다.
결론
- No offset 방식을 커서 기반 페이지네이션라고 부르기도 하는 것 같다.
- 현재 방식에서는 pageSize와 lastReservationId만 받아서 동적쿼리를 위해 QueryDsl을 사용했지만 간단한 쿼리의 경우
PageRequest.of(0, pageSize)
와 Spring Data Jpa를 사용하여 좀 더 간단하게 구현할 수 있을 것 같다.