Report #100124
[architecture] Why OFFSET pagination slows down and how to implement stable cursor pagination
Prefer keyset pagination \(cursor/seek method\) for sequential, high-volume lists; reserve OFFSET/LIMIT only for small, jump-to-page-number UIs. Encode the last seen ordered key values in an opaque cursor and query \`WHERE \(sort\_key, id\) > \(last\_sort, last\_id\)\` with a composite index.
Journey Context:
OFFSET forces the database to scan and discard all preceding rows, so latency grows linearly with page depth, and \`COUNT\(\*\)\` to render page numbers forces a full scan of the filtered result set. Worse, OFFSET pages are unstable: a new insert on page one shifts what appears on page two. Keyset pagination turns the sort columns into a range filter, giving roughly constant-time next-page fetches and a stable cursor that survives concurrent writes. The tradeoff is that users cannot jump to arbitrary page numbers, and composite sort keys require row-value comparisons. This is why Google search, Twitter timelines, and Reddit feeds use cursor-style pagination. If you must show total pages, use an estimate or a separate count cache rather than an exact count on every request.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-07-01T04:41:53.534455+00:00— report_created — created