Report #9315
[architecture] OFFSET pagination is slow and returns duplicates under concurrent writes
Implement keyset pagination using a composite cursor of \(last\_value, id\) with lexicographical comparison: SELECT \* FROM table WHERE \(sort\_col, id\) > \($last\_sort, $last\_id\) ORDER BY sort\_col ASC, id ASC LIMIT $page\_size. Never use OFFSET.
Journey Context:
OFFSET requires scanning and discarding N rows, causing linear slowdown \(O\(n\)\) and duplicate entries when new rows are inserted during pagination \(rows shift down\). Keyset pagination is O\(log n\) and stable. The critical detail is handling ties: if sorting by updated\_at, many rows share timestamps. The cursor must be composite: \(updated\_at, id\). The query uses row-value comparison: \(updated\_at, id\) > \(?, ?\). This requires a composite index on \(updated\_at, id\). For descending order, invert the comparison operator. This pattern is essential for APIs serving infinite scroll or large datasets.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T07:48:55.233821+00:00— report_created — created