Что такое курсорная пагинация
Что проверяют
Понимаете ли вы, почему постраничная выборка по смещению разваливается на больших глубинах и живых данных. За вопросом обычно стоит их собственная лента или выгрузка, где обход по номерам страниц начал тормозить и дублировать записи.
Как отвечать
Курсорная пагинация вместо номера страницы передаёт значения ключа сортировки последней выданной записи и берёт следующую порцию строго после неё. База не перебирает пропускаемые строки, а сразу встаёт в нужную точку индекса.
select * from orders
where (created_at, id) < (:last_created_at, :last_id)
order by created_at desc, id desc
limit 20;Два условия обязательны. Первое: ключ должен быть уникальным, поэтому к неуникальной дате добавляют идентификатор и сравнивают кортежем, иначе записи с одинаковым временем будут теряться или повторяться. Второе: индекс должен совпадать с порядком сортировки, иначе выигрыша не будет.
Почему лучше смещения: с offset сервер читает и отбрасывает все пропускаемые строки, и стоимость растёт с глубиной; к тому же вставка новой записи в начало ленты сдвигает границы страниц, и пользователь видит повтор. Курсор от обеих бед свободен.
В Java это чаще собственный запрос или Slice вместо Page, поскольку Page тянет за собой отдельный подсчёт всей выборки. Наружу курсор отдают непрозрачной строкой, чтобы клиент не собирал его сам.
Где спотыкаются
Берут курсором идентификатор последней записи, а сортируют по другому полю. Получается смесь двух порядков: часть строк выпадает, часть приходит дважды. Сравнивать надо ровно по тем колонкам, по которым идёт сортировка.
Утверждают, что назад листать нельзя. Можно: разворачивается знак сравнения и направление сортировки, а страница переворачивается на клиенте. Чего действительно нет, так это прыжка на произвольный номер страницы, и это осознанный размен.
Отдают клиенту сырой ключ и потом не могут поменять сортировку. Курсор привязан к порядку: стоит упаковать в него поля сортировки и версию формата, а при несовпадении отвечать ошибкой, а не молча выдавать другой срез.