Spring Data JPA: Page<T> with COUNT query vs Slice<T> for paginating a multi-million-row table
0 reputation · 11 Jul 2020, 13:07 UTC
0 reputation · 11 Jul 2020, 13:07 UTC
I'm designing a read endpoint over a table expected to hold tens of millions of rows, using Spring Data JPA (assume Spring Boot 3.x / Spring Data JPA 3.x). The API needs stable, bounded queries, but I'm torn between two documented approaches: returning Page<T> from a repository method with a Pageable, versus returning Slice<T> to skip the total-count query.
My understanding is that Page<T> triggers an additional COUNT query to populate totalElements and totalPages, which can become expensive on very large tables, while Slice<T> only fetches one extra row to determine whether a next page exists. The trade-off seems to be losing total-count metadata that a UI pager typically displays.
There is also the offset concern: deep pages via LIMIT/OFFSET degrade as offsets grow, and I'm unsure whether either abstraction mitigates that or whether keyset-style paging would be required instead.
Given a large dataset where clients mostly page forward:
Slice<T> the documented way to avoid the COUNT query, and what metadata do I actually lose?35275 reputation · 11 Jul 2020, 18:34 UTC
For a read‑only endpoint over a table that will hold tens of millions of rows, Slice<T> is the documented way to avoid the extra SELECT COUNT(*) that Page<T> triggers.
totalElements – the exact row count.totalPages – derived from the count and page size.The slice still returns hasNext() (based on fetching one extra row) and the current page number, but you cannot compute page numbers beyond the current slice without the total.
Both Page<T> and Slice<T> are built on Spring Data’s LIMIT/OFFSET implementation. The performance penalty that appears when the offset grows large is therefore present for either return type; choosing Slice<T> does not change the underlying query.
Slice<Entity> and accept a Pageable parameter.@Repository
public interface OrderRepository extends JpaRepository {
Slice findByStatus(String status, Pageable pageable);
}
PageRequest.of(page, size) (or a custom Pageable that sorts on a unique column).hasNext() and optionally the current page number; adapt the UI to use "load more" or infinite scroll instead of page numbers.If the UI must show total pages or a progress indicator, you need an alternative to the expensive COUNT(*). Options include:
pg_tuples_count or reltuples from pg_class).Only adopt one of these if you confirm that the exact count is required; otherwise, Slice<T> alone satisfies the bounded‑query goal.
The COUNT behavior of Page<T> and the hasNext logic of Slice<T> have not changed between Spring Data JPA 2.x and 3.x. No automatic optimization removes the COUNT query unless you supply a custom count query via @Query with a countQuery attribute.
spring.jpa.show-sql=true) and confirm that a Page call produces two statements (data + COUNT) while a Slice call produces only the data query plus one extra row.EXPLAIN ANALYZE on the generated COUNT query against a table with tens of millions of rows to observe its cost.Do you need to display the total number of rows or total pages to the client? If the answer is yes, the Slice<T> approach alone is insufficient and you will need an approximate‑count strategy; if the answer is no, Slice<T> is the recommended solution.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.