Пагинация в API — шесть распространённых схем
Контекст: разбор REST URL, восемь принципов RESTful API. Углубление: проектирование API, документирование в OpenAPI. Параллель в SQL: LIMIT и ключевая выборка.
Зачем пагинация
Эндпоинты вроде GET /orders или GET /users редко отдают всю таблицу целиком: ответ раздувается, растёт время ответа и нагрузка на БД. Пагинация — договорённость, как клиент запрашивает порцию записей и как сервер сообщает, есть ли ещё данные.
В одном API обычно выбирают одну основную схему для всех коллекций (page/size или cursor), чтобы SDK и документация оставались предсказуемыми. Ниже — шесть распространённых подходов; на практике их комбинируют (гибрид), но клиенту важно видеть явный контракт.
1. Offset-based (смещение и лимит)
Сервер пропускает первые offset строк и возвращает не больше limit записей.
GET /v1/orders?offset=0&limit=3
GET /v1/orders?offset=3&limit=3
| Плюсы | Минусы |
|---|---|
Простая реализация (LIMIT/OFFSET в SQL) | На больших offset запросы в БД дорогие |
| Удобно «перейти на страницу N» в UI | При вставках и удалениях между запросами возможны дубликаты и пропуски |
| Понятно в логах и Postman | Нужен стабильный ORDER BY, иначе порядок строк «плывёт» |
Когда уместно: админки, отчёты, каталоги с редкими изменениями, небольшие смещения.
2. Page-based (номер страницы)
Абстракция над offset: клиент передаёт номер страницы и размер, сервер сам считает смещение (offset = (page - 1) * size).
GET /v1/orders?page=2&size=3
Семантика та же, что у offset-based: страница 2 при size=3 — это записи с индексами 3, 4, 5 в отсортированном списке. Удобно для кнопок «Страница 1, 2, 3…» в интерфейсе.
В ответе часто возвращают метаданные:
{
"items": [ /* … */ ],
"page": 2,
"pageSize": 3,
"total": 128
}
Поле total требует отдельного COUNT(*) — на очень больших таблицах его иногда убирают или кэшируют.
3. Cursor-based (курсор)
Клиент не знает «номер страницы», а передаёт непрозрачный токен — позицию в упорядоченной выборке. Сервер кодирует в курсоре последний увиденный ключ и направление сортировки.
GET /v1/orders?limit=3
GET /v1/orders?cursor=eyJpZCI6MTAyLCJzIjoiYXNjIn0&limit=3
Пример ответа:
{
"items": [ /* id 100, 101, 102 */ ],
"pagination": {
"nextCursor": "eyJpZCI6MTAyLCJzIjoiYXNjIn0",
"hasMore": true
}
}
| Плюсы | Минусы |
|---|---|
| Устойчивее при вставках в «хвост» ленты | Нельзя перейти на произвольную страницу без полного обхода |
| Хорошо для лент, чатов, таймлайнов | Курсор привязан к сортировке; смена sort ломает продолжение |
| Меньше «дыр» при активных изменениях, чем у чистого offset | Токен нужно подписывать или проверять, чтобы клиент не подделал границу |
Когда уместно: соцсети, уведомления, синхронизация «что нового с момента X».
4. Keyset-based (пагинация по ключу)
Клиент явно указывает границу по индексируемому полю (часто первичный ключ или пара (created_at, id)). Это открытый вариант cursor: параметры читаемы в URL.
GET /v1/orders?after_id=102&limit=3
GET /v1/orders?created_at_lt=2025-11-14T00:00:00Z&limit=50
На сервере запрос похож на SQL:
SELECT * FROM orders
WHERE id > :after_id
ORDER BY id ASC
LIMIT 3;
Требования:
- фиксированный
ORDER BYпо полям с индексом; - для составного ключа сортировка и условие в
WHEREсогласованы ((created_at, id)).
Тот же приём в REST и в SQL — в главе про LIMIT, OFFSET и ключевую выборку.
5. Time-based (по времени)
Выборка ограничивается временным окном — удобно для логов, событий, метрик, истории заказов.
GET /v1/orders?start_time=2024-01-03T00:00:00Z&end_time=2024-01-05T23:59:59Z&limit=100
Часто сочетают с keyset или cursor внутри окна: сначала фильтр по дате, затем after_id или cursor для следующей порции внутри диапазона.
Важно зафиксировать часовой пояс (Z / +03:00) и формат (date-time в OpenAPI).
6. Hybrid (гибрид)
Объединяют два и более механизма — типично временное окно + курсор, чтобы и сузить данные, и стабильно листать внутри среза.
GET /v1/orders?cursor=abc&start_time=2024-01-03T00:00:00Z&end_time=2024-01-05T23:59:59Z&limit=50
Сценарий: отчёт «заказы за неделю» с подгрузкой ленты; курсор держит позицию, пока в том же окне добавляются записи.
page, и cursor одновременно (лучше — явная ошибка 400).Сравнение и выбор схемы
| Схема | Пример query | Произвольная страница | Устойчивость при изменениях | Нагрузка на БД |
|---|---|---|---|---|
| Offset | offset=30&limit=10 | да | слабая | растёт с offset |
| Page | page=4&size=10 | да | слабая | как offset |
| Cursor | cursor=…&limit=10 | нет | сильная | обычно низкая |
| Keyset | after_id=102&limit=10 | нет | сильная | низкая при индексе |
| Time | start_time=…&end_time=… | зависит от пары | средняя | зависит от индекса по времени |
| Hybrid | окно + cursor | частично | сильная в окне | зависит от запроса |
Практическая эвристика:
- публичный каталог с кнопками страниц → page/size или offset/limit;
- мобильная лента, чат, «подгрузить ещё» → cursor или keyset;
- архив событий за период → time-based + keyset/cursor внутри;
- B2B с жёстким SLA и большими таблицами → keyset по
(sort_field, id).
Метаданные ответа и HTTP-заголовки
Клиенту нужно понимать, как запросить следующую порцию:
- в теле JSON —
nextCursor,hasMore,total,page,pageSize; - в заголовке
Link(RFC 5988) —rel="next",rel="prev",rel="first",rel="last"; - опционально
X-Total-Count— общее число элементов (осторожно на миллионах строк).
Пример заголовка:
Link: <https://api.example.com/v1/orders?cursor=eyJ...>; rel="next"
Для HATEOAS-зрелости API достаточно поля _links.next в JSON — см. модель Ричардсона.
Описание в OpenAPI
Параметры пагинации — обычные in: query с schema, default и description. Пример для page-based списка:
paths:
/orders:
get:
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: Номер страницы (начиная с 1)
- name: size
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
description: Записей на странице
responses:
'200':
description: Страница заказов
content:
application/json:
schema:
$ref: '#/components/schemas/OrderPage'
Для cursor-based в description укажите: курсор непрозрачен, выдаётся в pagination.nextCursor, повторное использование старого курсора после смены сортировки недопустимо.
Разбор полей summary, description, схем ответа с total/page — в документировании API в OpenAPI.
Проверка в Postman и curl
curl -s "https://api.example.com/v1/orders?offset=0&limit=3" -H "Accept: application/json"
curl -s "https://api.example.com/v1/orders?cursor=TOKEN" -H "Accept: application/json"
Сценарии с пагинацией удобно собирать в коллекции Postman — см. Postman и curl.
Связанные материалы
| Тема | Статья |
|---|---|
| Фильтрация, сортировка, поиск в API | 8.05. Проектирование API |
| REST URL и единый язык query | API — интерфейсы |
| Паттерн «Итератор» и пагинация в коде | Итератор в Java |
Пример GitHub API с Link | Python — HTTP-клиент |
Контрольные вопросы
- Почему при offset-пагинации между запросом страницы 1 и страницы 2 может появиться дубликат?
- Чем keyset отличается от opaque cursor с точки зрения клиента?
- Зачем пагинации в SQL и API нужен стабильный
ORDER BY? - Какие поля ответа вы бы задокументировали для cursor-based списка заказов?
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Интеграция - это когда две программы умеют разговаривать друг с другом и делать общее дело. Выбор модели взаимодействия определяет архитектурные свойства системы — отзывчивость, устойчивость к сбоям, сложность отладки и масштабируемость. Интеграционные потоки данных - как моделируются маршруты сообщений, преобразования и оркестрация обмена между системами. Интеграционная авторизация: сравнение JWT и API-ключей, потоки M2M, OAuth Client Credentials, mTLS и service accounts. Управление сессиями в распределённых системах - согласование состояния между сервисами, паттерны саг и компенсационные операции. История интеграционных технологий - эволюция от RPC и CORBA к современным API, шинам сообщений и событийной архитектуре. Веб-сервис - это программа, которая живёт на сервере и отвечает на запросы других программ через интернет. Мы её не видим (нет никакой кнопки или картинки), но наше приложение с ней разговаривает. Модель запрос-ответ в интеграции систем - как сервисы принимают входные события, обрабатывают их и возвращают результат внешним участникам. API как контракт и структура HTTP-запроса; SDK — набор инструментов для разработки; REST, OpenAPI и обзор других стилей API. HTTP-запрос, HTTPS, HTTP/2–3, QUIC и карта HTTP-экосистемы для разработки и инфраструктуры. Асинхронная коммуникация между сервисами - когда отправитель не ждёт немедленного ответа и как это повышает устойчивость системы. Реактивные взаимодействия фокусируются на обмене событиями в режиме реального времени. Системы реагируют на события по мере их возникновения, обеспечивая непрерывный поток данных.Интеграция
Типы взаимодействия между системами
Интеграционные потоки данных
Авторизация в интеграционных сценариях
Управление сессиями в распределённых системах
История развития интеграционных технологий
Веб-сервисы
Модель запрос-ответ в сетевом взаимодействии
API - интерфейсы прикладного программирования
HTTP как основа веб-интеграций
Асинхронная коммуникация между сервисами
Реактивные системы и потоки данных