목록 조회 API를 만들면서 처음에는 Spring Data JPA의 Page를 자연스럽게 사용했다. 전체 페이지 수와 전체 데이터 수를 함께 내려줄 수 있어서 편리했기 때문이다.
하지만 코드 리뷰를 받으면서 Page가 항상 좋은 선택은 아니라는 점을 다시 검토했다. 처음에는 목록 응답이면 습관처럼 Page를 고르는 편이었는데, 화면에서 실제로 필요한 정보는 전체 개수가 아니라 다음 데이터가 더 있는지 여부인 경우도 많았다. Page는 콘텐츠 조회 쿼리뿐 아니라 전체 개수를 구하기 위한 count 쿼리도 함께 수행한다.
데이터가 많아지거나 조건이 복잡해질수록 이 count 쿼리가 부담이 될 수 있었다. 이 글은 Page와 Slice를 어떤 기준으로 선택할지 정리한 기록이다.
문제 상황
count 쿼리는 단순해 보이지만 경우에 따라 가볍지 않을 수 있다. 검색 조건이 복잡하거나, 조인이 들어가거나, 데이터 수가 많아지면 전체 개수를 세는 비용이 커질 수 있다.
특히 사용자 화면에서 꼭 전체 페이지 수가 필요하지 않은 경우에도 관성적으로 Page를 쓰면 불필요한 count 쿼리가 나갈 수 있다.
예를 들어 무한 스크롤이나 "더 보기" 방식의 목록에서는 전체 페이지 수보다 다음 데이터가 더 있는지만 알면 충분할 수 있었다.
해결 방법
Page는 현재 페이지 데이터와 함께 전체 데이터 수, 전체 페이지 수를 제공한다. 관리자 화면이나 정확한 페이지 이동이 필요한 화면에서는 유용하다.
반면 Slice는 전체 개수를 세지 않고, 다음 데이터가 있는지 여부를 중심으로 다룬다. 보통 요청한 size보다 하나 더 가져와 다음 페이지가 있는지 판단하는 방식으로 사용할 수 있었다.
따라서 사용자가 전체 페이지 수를 알아야 하는지에 따라 선택이 달라질 수 있다.
선택할 때 본 것
프로젝트에서는 아래 내용을 보고 Page와 Slice를 구분하려고 했다.
- 전체 개수와 전체 페이지 수가 화면에 필요한가
- 사용자는 특정 페이지 번호로 이동해야 하는가
- 무한 스크롤이나 더 보기 방식인가
- 검색 조건이나 조인이 복잡해 count 비용이 커질 수 있는가
- 정확한 개수보다 응답 속도를 더 봐야 하는가
이 관점에서 단순 목록이나 무한 스크롤 성격이 강한 API는 Slice가 더 적절할 수 있었다. 반대로 관리자 화면처럼 전체 개수가 필요한 경우에는 Page를 쓰는 것이 자연스러웠다.
확인한 내용
이 경험 이후로는 페이지네이션을 구현할 때 Page를 기본값처럼 바로 사용하지 않으려고 한다.
목록 조회 API를 만들면 먼저 화면 요구사항을 확인한다. 전체 개수가 꼭 필요한지, 다음 데이터가 있는지만 알면 되는지 먼저 확인한다. 그다음에 Page와 Slice를 선택한다.
성능 최적화라고 해서 복잡한 튜닝부터 해야 하는 것은 아니었다. 필요 없는 count 쿼리를 만들지 않는 것처럼, 기본 선택을 다시 보는 것만으로도 비용을 줄일 수 있다.
정리
페이지네이션은 단순히 데이터를 나눠서 내려주는 기능처럼 보이지만, 어떤 정보를 함께 내려줄지에 따라 쿼리 비용이 달라질 수 있다.
이번 작업을 통해 Page와 Slice는 편의성 차이만이 아니라 요구사항과 비용 차이도 함께 봐야 한다는 점을 확인했다.