TGViewer
Женя Янченко Женя Янченко @jane_yanchenko · 5.51K subscribers
Post #406 2.17K
➡️ Keyset-пагинация (seek-метод)

Если при OFFSET мы говорим БД:
"пропусти первые 70 000 записей и дай следующие 500",


то при Keyset говорим:
"дай следующие 500 после той записи, на которой мы закончили прошлый раз".


То есть выборка опирается на уникальное поле последнего элемент предыдущей страницы.

Упрощенный пример:

SELECT ...
FROM payments
WHERE id > 70000
ORDER BY id
LIMIT 500;


При таком запросе время обработки не увеличивается пропорционально номеру запрашиваемой страницы, потому что базе не нужно проходить записи предыдущих страниц, она сразу начинает выборку с указанной строки.

Для этого на ключевом поле (в упрощенном примере - это id) должен быть индекс. В примере предполагается, что id упорядочены.


Часто используется сортировка по дате создания и id:

SELECT ...
FROM payments
WHERE (created_at, id) < ('2026-09-08 12:30:00', 70000)
ORDER BY created_at DESC, id DESC
LIMIT 500;


Здесь для эффективной работы нужен составной индекс по (created_at, id).

Также keyset решает проблему с дублирующимися записями в следующем ответе, которая возникает в случае вставки новых записей между запросами.


💭 Что в случае keyset пагинации передает фронт

Для простого случая с сортировкой только по id:

GET /payments?limit=500&lastId=1000


Упрощенный ответ бэка:
{
"items": [...],
"lastId": 1500 //если идем по возрастанию
}


Keyset-пагинация — это способ выбрать следующую страницу.

Cursor-based пагинация — это подход к реализации API, в котором клиент получает некий курсор и возвращает его серверу.

Бэк берет нужные поля, упаковывает их в ДТО, кодирует его (например Base64URL, а при необходимости подписывает или шифрует) и отдает:

{
"items": [...],
"cursor": "eyJpZCI6ODQyMX0"
}


Фронт в следующий запрос прикладывает полученный курсор:
GET /payments?limit=500&cursor=eyJpZCI6ODQyMX0


Бэк его раскодирует и действует согласно своей логике.

Это удобно тем, что бэк может поменять поля в ДТО по своему усмотрению, и это не потребует никаких доработок от фронта, ему как приходила строка, так и приходит. Правда, может понадобиться поддержать обратную совместимость со старым курсором.


С помощью keyset-пагинации нельзя реализовать переход на конкретную страницу, но зато удобно делать бесконечный скролл 📱


Плюсы:

➕ Стабильная производительность даже при миллионах строк при подходящем индексе. Нет замедления по мере продвижения по списку.

➕ Нет проблем со сдвигами, если данные добавляются

➕ Удобно для бесконечного скролла


В случае OFFSET пагинации на UI часто показывают общее количество страниц. Для получения totalElements обычно выполняют дополнительный SELECT COUNT(*).

Если мы реализовали keyset-пагинацию и хотим в ответе бэка сообщить, есть ли еще данные или это конец списка, то это можно реализовать без дополнительных запросов в БД.

Просто делаем исходный запрос с LIMIT на 1 больше:

... LIMIT 501;

Если вернулась 501 строка, то отвечаем:
{
"items": [...], // тут запрошенные 500
"hasNext": true // есть еще записи, запрашивайте
}

Если вернулось меньше 501 строки, то отвечаем:
{
"items": [...], // тут запрошенные 500
"hasNext": false // больше записей нет
}


Но у keyset-пагинации есть и минусы.

При произвольных сортировках преимущество keyset может сильно уменьшиться. Keyset опирается на индекс, а мы не можем создавать индексы на все комбинации полей.

Поэтому для keyset API часто заранее ограничивают допустимые варианты сортировки и создают индексы под несколько востребованных сценариев. Да и NULL в полях сортировки усложняет keyset.

Есть библиотеки, которые поддерживают keyset-пагинацию для разных языков.
Можно написать свою обертку или конкретную функцию для таких случаев.

Подытожим минусы:

➖ сложнее реализация
➖ нельзя сделать переход на конкретную страницу
➖ для эффективной работы ограничивают варианты сортировки

Когда использовать Keyset-пагинацию:

✅ на UI бесконечный скролл

✅ список часто пополняется новыми записями

✅ для API c большими коллекциями, где клиент последовательно выгружает данные и ему не нужен переход сразу на страницу 456
  • 🔥 23
  • 👍 12
  • ❤ 11
  • ❤‍🔥 3
More from @jane_yanchenko
  1. Sep 21, 2026🔗 Подборка постов про Кафку Как обещала на стриме, собрала посты про Кафку в удобное огла…
  2. Sep 21, 2026🎞 Готова запись стрима про Кафку: https://youtu.be/2aRKsD-MWDA Большое спасибо всем, кто…
  3. Sep 16, 2026Сегодня стрим по Кафке в 19:00 Планируем не в формате доклада, а в формате вопрос-ответ, ч…
  4. Sep 16, 2026Post #413
  5. Sep 16, 2026Post #412
  6. Sep 16, 2026Post #411
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →