목록 조회 API를 만들면서 처음에는 Spring Data JPA의 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는 편의성 차이가 아니라 요구사항과 비용의 차이로 봐야 한다는 기준이 생겼다.