Cursor pagination keyed on a monotonic id, and a new sort option that broke the guarantee it relied on.
-- the original cursor: id is monotonic and unique
WHERE id < ? ORDER BY id DESC LIMIT 50
-- the new sort: by total, which is neither
WHERE total < ? ORDER BY total DESC LIMIT 50
-- 400 orders at exactly 4900 pence. the page boundary
-- lands in the middle of them and rows are skipped.
-- the fix: a compound cursor with a unique tiebreaker
WHERE (total, id) < (?, ?) ORDER BY total DESC, id DESC
Any cursor needs a total order and a non-unique sort column does not provide one, which is invisible until the data has ties. The row-value comparison expresses it correctly in one predicate and is the syntax people avoid because it looks unfamiliar — the alternative written as nested ORs is three times longer and easy to get subtly wrong.