PageNumberPagination or CursorPagination when rows shift during a zero-downtime backfill
0 reputation · 28 Nov 2022, 16:46 UTC
0 reputation · 28 Nov 2022, 16:46 UTC
A small DRF service requires a pagination change applied without downtime while a backfill concurrently inserts or updates rows. The current endpoint uses PageNumberPagination, which exposes page and page_size parameters and returns a total count, but translates page numbers into OFFSET/LIMIT so a page's contents can shift between requests. CursorPagination avoids OFFSET drift by encoding ordering values into an opaque cursor, yet it does not return a total count by default and exposes only the cursor parameter. It also requires a unique, indexed ordering field—commonly the primary key—because it does not automatically append a tiebreaker to non-unique columns.
The unresolved decision is whether keyset stability justifies breaking the client contract: clients that jump to arbitrary page numbers or display total counts will break, and supporting both pagination styles during a transition adds complexity. Version-sensitive defaults such as page_size, max_page_size, cursor payload, and count presence must be confirmed against the installed DRF release rather than assumed.
Which documented approach minimizes silent data shifts for clients that only page forward, and what is the least disruptive way to expose the new ordering without forcing an immediate client migration? Should the transition keep the old paginator on the existing viewset and introduce the new ordering through a separate endpoint or query parameter until clients have migrated? How can the ordering field be made unique and indexed without extending the downtime window?
33275 reputation · 28 Nov 2022, 18:56 UTC
To minimize silent data shifts for forward-paging clients during a zero-downtime backfill, CursorPagination is the documented approach. To avoid breaking existing client contracts, maintain PageNumberPagination on the current viewset and expose the new cursor-based ordering via a separate endpoint or a query parameter toggle. Ensure the ordering field is unique by appending the primary key as a tiebreaker and create the index using a concurrent build operation to avoid table locks.
The instability in PageNumberPagination stems from its reliance on OFFSET. When a backfill inserts or updates rows, the relative position of records shifts, causing clients to see duplicate items or skip records entirely as they move from page to page.
Confirmed Behaviors:
CursorPagination uses keyset pagination, encoding the last seen value into an opaque cursor, which remains stable regardless of row shifts.CursorPagination requires a unique, indexed ordering; it does not automatically provide a tiebreaker for non-unique columns.CursorPagination does not return a total count, which is a breaking change for clients relying on PageNumberPagination's count metadata.PageNumberPagination./api/items/stream/) that reuses the same serializer but employs CursorPagination.?pagination=cursor) within the view to switch paginators dynamically. This requires rigorous testing to ensure identical filtering and ordering logic.['-created_at', '-id'].CREATE INDEX CONCURRENTLY idx_items_cursor ON items (created_at DESC, id DESC);
This recommendation assumes the use of a database that supports concurrent index creation (like PostgreSQL). Because DRF defaults for page_size and max_page_size are version-dependent, you must verify these against your specific installed release notes.
Required Diagnostic: Is the current ordering column already unique and indexed? If so, a new index may be unnecessary; if not, the composite index described above is mandatory for CursorPagination to function.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.