Add base support for keyset pagination.
Review Request #15274 — Created Sept. 4, 2026 and updated — Latest diff uploaded
Django's database paginators work by tracking what page you're in and
generating new SQL usingOFFSETandLIMITto select the right page
of results to return. This has a major performance impact the further in
you go, since the database often has to fetch the values first before
calculating the offset into it. With heavy usage and deep lists of
pages, this gets expensive fast.Keyset pagination is an alternative approach that replaces page-based
offsets with "cursors". A cursor is a representation of a filter query
that selects all entries before or after the current page of results.
This involves selecting using sortable criteria (such as a timestamp)
along with a "PK tie-breaker" (using a sortable row ID as the last
criteria in case the rows otherwise match), and a query that's sorted in
the same direction.With this in place, the paginator can encode a cursor that says (for
example), "Select results after this last result's timestamp and PK."
Or when traversing backwards, "Select results before his first result's
timestamp and PK."There are some tradeoffs. You can't jump to arbitrary pages (though
there's a trick here) and you can't effectively get the full result
count (as that can have the same expense that pagination has for
filtered results). This usually leads to UIs that just have "Previous"
and "Next" buttons.The trick to jumping to page numbers is to precompute some number of
pages in advance in either direction within the initial query. This
makes it easy to generate a series of cursors based off of that data in
order for the UI to provide the next few page numbers. This is fairly
cheap, especially in cases like the datagrid where the query is fetching
just PKs or other cheap columns and then following up with a full data
fetch.This change implements the base support for all this. Datagrid and API
usage will follow.
Unit tests pass.
Made use of this in an upcoming change for datagrids.