Offset Pagination vs. Keyset Pagination for Deep-Page Retrieval
0 reputation · 11 Apr 2021, 05:23 UTC
0 reputation · 11 Apr 2021, 05:23 UTC
When implementing pagination in PostgreSQL for tables with millions of rows, the choice of retrieval method impacts both performance and user experience. The primary constraint is the need to maintain consistent response times as users navigate deeper into the result set.
Offset-based pagination allows users to jump to a specific page number, but it requires the database to scan and discard all preceding rows. In contrast, keyset pagination uses a cursor based on a unique, sorted column to fetch the next set of results, ensuring constant-time performance regardless of the page depth.
However, keyset pagination restricts navigation to sequential movements and requires a strictly ordered, non-null index to remain efficient.
Which approach is more sustainable for a dataset where deep-page access is frequent but random page jumping is a secondary requirement? Under what specific indexing conditions does keyset pagination lose its performance advantage over offset?
35275 reputation · 11 Apr 2021, 13:02 UTC
For a dataset where deep-page access is frequent and random page jumping is secondary, keyset pagination is more sustainable than OFFSET pagination. Keyset pagination loses its performance advantage over OFFSET when the ordering cannot be expressed as a stable, unique, indexed range seek, e.g., a non-unique sort column without a tie-breaker, a missing or non-covering index on the sort key, or queries with filters that prevent an index seek to the cursor position.
Deep OFFSET pages become slower and unstable under concurrent inserts and deletes, often causing duplicates or skipped rows between requests. Keyset traversal stays near constant cost and monotonic when ordering columns are indexed and unique. OFFSET remains acceptable for shallow pages, small tables, or admin exports where simplicity outweighs deep-page cost.
One diagnostic that changes the recommendation: Is your sort column unique and immutable and is the keyset index covering the query filters? If filters are applied on columns not in the index, the planner may fall back to scans and the advantage narrows.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 11 Apr 2021, 13:12 UTC
In Hibernate, setFirstResult() and setMaxResults() always translate to SQL OFFSET and LIMIT. If you add a keyset WHERE clause but still call those methods, PostgreSQL performs the seek and then discards the offset rows anyway, so deep-page cost stays linear. True keyset pagination requires omitting Hibernate's pagination methods and binding the cursor values directly in the predicate.
Also confirm the index exactly matches the ORDER BY clause—column order, sort direction, and NULLS FIRST/NULLS LAST. A mismatch makes the planner sort instead of seek. Run EXPLAIN ANALYZE and check for an index scan without a large "rows removed by filter" count to verify the plan.