Report #6325
[architecture] Pagination drifting or duplicates when iterating high-throughput time-series data with OFFSET
Replace OFFSET/LIMIT with keyset pagination \(cursor-based\) on an immutable composite key: \(created\_at DESC, id DESC\). Never paginate on volatile columns like 'updated\_at' or float timestamps alone.
Journey Context:
OFFSET is O\(n\) and suffers from drift: if 10 new rows are inserted between page 1 and page 2, row N appears again at the top of page 2, or is skipped entirely. Keyset pagination uses the last seen tuple as a bookmark: WHERE \(created\_at, id\) < \(last\_ts, last\_id\). This is index-only, stable, and constant time. Common pitfall: using only created\_at fails when multiple rows share the same timestamp \(guaranteed in high throughput\), causing the cursor to skip all but one row with that timestamp—hence the composite with a strictly unique tie-breaker \(ULID, UUID, bigserial\). Tradeoff: you lose the ability to jump to arbitrary page numbers \(no 'Page 5 of 100' UI\), requiring infinite-scroll or 'Next/Prev' only. Calculating total counts is expensive; use approximate counts \(pg\_class in PG, SHOW TABLE STATUS in MySQL\) or defer counts.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T23:46:36.833609+00:00— report_created — created