Report #75146
[architecture] Offset pagination \(LIMIT/OFFSET\) skipping or duplicating items during concurrent writes and poor performance on deep pages
Adopt keyset pagination \(cursor-based\) using an immutable, monotonic composite key \(e.g., \(created\_at, id\)\), encoding the last seen tuple as an opaque cursor in the WHERE clause: WHERE \(created\_at, id\) > \($1, $2\), eliminating OFFSET entirely.
Journey Context:
OFFSET n instructs the database to scan and discard n rows; this is O\(offset \+ limit\) and degrades linearly as users paginate deeper \(page 1000 is 100x slower than page 10\). Worse, if rows are inserted/deleted while the user pages, the window shifts: a new row on page 1 pushes the last row of page 2 to page 3, so the user sees it twice when clicking 'next'. Cursor pagination uses the last seen value \(timestamp or ID\) as a filter: WHERE created\_at > '2023-01-01'. This is O\(log n\) via index and stable against concurrent writes. The tradeoff is inability to jump to arbitrary page numbers \(no 'go to page 50'\) and requirement for an immutable, unique ordering column \(use composite \(created\_at, id\) if timestamps aren't unique\). This is the standard for high-performance APIs \(Twitter, Slack, GraphQL Relay spec\) and is documented in SQL performance literature.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T08:43:38.470281+00:00— report_created — created