Report #9886
[architecture] OFFSET pagination is slow and returns inconsistent results under concurrent writes
Implement keyset pagination \(seek method\): encode the last row's values \(e.g., \[created\_at, id\]\) into a base64 cursor. Query with WHERE \(created\_at, id\) > \($1, $2\) ORDER BY created\_at, id. Never expose raw page numbers.
Journey Context:
LIMIT/OFFSET is intuitive for 'page 2' links, but OFFSET forces the database to scan and discard all preceding rows, making latency linear with page depth. Worse, if a row is inserted or deleted while the user navigates, the result window shifts: item 20 becomes item 19, causing items to be skipped or duplicated across pages. Keyset pagination \(the 'seek method'\) treats the dataset as a linked list. After fetching a page, encode the last row's sort values \(e.g., timestamp and UUID\) into an opaque cursor \(base64 JSON\). The next query uses a WHERE clause like \(created\_at, id\) > \(last\_ts, last\_id\), which efficiently uses a composite index to seek directly to the next position in O\(log n\) time. This is stable against concurrent writes and constant-time regardless of depth. The tradeoff is that you cannot jump to arbitrary page numbers \(e.g., 'go to page 50'\), so it suits infinite-scroll UIs, not page-number navigation.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T09:18:37.083916+00:00— report_created — created