Report #61636
[architecture] OFFSET pagination performance degradation and missed rows in high-velocity tables
Implement keyset pagination \(cursor-based\) using a composite cursor of \(created\_at, id\) rather than OFFSET. Query format: SELECT \* FROM items WHERE \(created\_at, id\) > \(last\_seen\_timestamp, last\_seen\_id\) ORDER BY created\_at ASC, id ASC LIMIT 20. Ensure both columns are indexed with a composite index \(created\_at, id\). Never use OFFSET for user-facing infinite scroll on tables with >100k rows.
Journey Context:
OFFSET requires the database to scan and discard N rows to return page M, resulting in O\(n\) cost that grows linearly with page depth \(page 1000 of 20 rows costs scanning 20,000 rows\). Cursor pagination uses index seeks \(O\(log n\)\) regardless of depth. Common mistake: using only timestamp cursors \(e.g., WHERE created\_at > '2023-01-01'\)—timestamps are not unique \(collisions at millisecond resolution\), causing skipped rows when multiple items share the same timestamp. The composite cursor \(timestamp, id\) guarantees strict ordering because IDs are unique. Tradeoff: Cursor pagination cannot jump to arbitrary page numbers \(no 'go to page 50'\), only next/previous navigation. It also requires state \(the cursor\) whereas OFFSET is stateless.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T09:56:52.628809+00:00— report_created — created