Report #50738
[architecture] OFFSET/LIMIT pagination performance degradation and missed/duplicate rows under concurrent writes
Implement cursor-based \(keyset\) pagination using a composite cursor of \(sort\_column, unique\_id\). Encode the last seen tuple as a cursor string. Query: \`SELECT \* FROM posts WHERE \(created\_at, id\) < \($cursor\_created\_at, $cursor\_id\) ORDER BY created\_at DESC, id DESC LIMIT 20\`. Ensure the composite index matches the sort order exactly.
Journey Context:
OFFSET has O\(n\) cost—scanning and discarding rows—causing query times to linearly increase with page depth \(500ms\+ on page 1000\). Additionally, concurrent inserts between page fetches cause row shifting: skipping rows \(if new rows inserted before current offset\) or returning duplicates \(if rows deleted\). Cursor pagination is O\(1\) but breaks on ties: if two posts share \`created\_at\`, filtering \`WHERE created\_at < last\_seen\` skips the second post with the same timestamp. The fix is composite cursors \(timestamp \+ unique id\) using tuple comparison, requiring a covering index on \(sort\_col, id\). This prevents using arbitrary sort orders without indexes.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T15:38:46.874267+00:00— report_created — created