Report #41209
[architecture] OFFSET/LIMIT pagination causes O\(n\) performance degradation and drift with concurrent writes on large tables
Implement keyset pagination \(seek method\) using the last seen tuple as a cursor: WHERE \(created\_at, id\) > \(last\_created\_at, last\_id\) ORDER BY created\_at ASC, id ASC LIMIT page\_size. Encode the cursor as an opaque base64 string to prevent tampering, and never expose raw database IDs directly. Note that MySQL requires explicit row-value syntax emulation \(a > 1 OR \(a = 1 AND b > 2\)\) as it lacks PostgreSQL's \(a,b\) tuple comparison.
Journey Context:
OFFSET forces the database to scan and discard N rows before returning data, becoming linearly slower as users paginate deeper. It also suffers from 'drift': if a row is inserted/deleted on a prior page, subsequent pages shift, causing skipped or duplicate items during concurrent writes. Keyset pagination maintains O\(log n\) performance using indexed seeks, but sacrifices the ability to jump to arbitrary page numbers \(only next/previous\) and requires immutable sort keys. The compound tuple comparison syntax varies by dialect, necessitating query builder abstraction to handle PostgreSQL's row constructor versus MySQL's boolean logic expansion.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T23:38:24.839952+00:00— report_created — created