QueryDSL으로 페이징 구현하기
개요
페이지네이션에 대해 알아보다 보면 상황에 따라 offset 기반과 cursor 기반의 방식으로 구현할 수 있습니다.
이때 offset 방식을 활용하여 QueryDSL으로 쿼리를 구현해보고자 합니다.
Offset과 Limit 그리고 성능
SELECT *
FROM TABLE_NAME
LIMIT 10
OFFSET 5000;
offset절은 만약 N이 들어온다면 N번째행부터 읽겠다는 의미입니다.
예를 들어 5000이라면 5000번째 행부터 읽겠다는 의미입니다.
Limit 절은 쿼리 결과에서 지정된 순서에 위치한 레코드만 가져오고자 할 때 사용됩니다.
Limit에는 클라이언트가 요청한 크기 N이 들어가게 되고 offset을 통해 가져온 전체 데이터에서 상위 N개를 가져옵니다.
위 둘을 조합하게 되면 5000번째부터 10번째 레코드만 읽겠다는 의미입니다.
이때 offset이 점점 커지게 되면 조회비용이 증가하게 되어 주의해야 합니다.
N개 결과의 row만 DB에서 조회하는것이 아니라 모든 row를 디스크로부터 읽어오기 때문에 데이터베이스에 많은 부하를 주게 됩니다.
예를 들어 5000~5010번째 데이터를 가져오는 것이 아니라 1~5010번째 데이터를 디스크에서 읽어옵니다.
Spring에서 실행되는 쿼리
SELECT *
FROM TABLE
OFFSET 5000 ROWS
FETCH FIRST 10 row only;
실제 날라가는 쿼리는 위와 같아서 찾아보니 limit offset 방식과 실행계획은 동일하게 보입니다.
구현
override fun selectDeliveryInfos(condition: Condition, pageable: Pageable, pagingQuery: PagingQuery): Page<DeliveryJpaEntity> {
val delivery: QDeliveryJpaEntity = QDeliveryJpaEntity.deliveryJpaEntity
val content = jpaQueryFactory.select(delivery)
.from(delivery)
.where(betweenDateConditionType(condition))
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.orderBy(setOrderByQuery(pagingQuery.sort))
.fetch()
val count: Long = jpaQueryFactory
.select(delivery.count())
.from(delivery)
.where(betweenDateConditionType(condition))
.fetchOne() ?: 0
return PageImpl(content, pageable, count)
}
private fun setOrderByQuery(sortCondition: String): OrderSpecifier<*> {
//TODO: sortCondition에 따라 분기문 추가 현재는 createdAt만 오름차순(날짜 빠른순)으로 지원
val delivery: QDeliveryJpaEntity = QDeliveryJpaEntity.deliveryJpaEntity
return OrderSpecifier(Order.ASC, delivery.createdAt)
}
offset과 limiet을 주고 orderBy의 경우에는 OrderSpecifier를 활용하여 지정하였습니다.
ORderSpecifier는 동적정렬에서 활용됩니다.
count 쿼리는 별도로 작성하였고 추후 데이터가 쌓여가면서 느려질 수 있기 때문에 비즈니스 상황에 따라 no offset 방법이나 트리거를 활용하여 count 수를 따로 관리해 주는 방안으로 빠르게 개선할 수 있을 것 같습니다.
혹은 offset대신 다음과 같은 방식도 고려해 볼 수 있습니다.
SELECT ...
FROM ...
WHERE ...
AND id < ?last_seen_id
ORDER BY id DESC
FETCH FIRST 10 ROWS ONLY
이러한 방법을 seek method 또는 keyset pagination이라고 부릅니다.
참고자료
https://thalals.tistory.com/298
https://jojoldu.tistory.com/528
https://brownbears.tistory.com/582