Пагинация в мобильной разработке — что это, виды и принцип работы

Автор: IT Sectr Опубликовано: 2026-03-11 Время чтения: 9 мин

Пагинация — техника постраничной загрузки данных, используемая в мобильных приложениях и веб-сервисах для работы с большими наборами записей. Согласно Android Developers Documentation (2025), правильная реализация пагинации снижает нагрузку на API, экономит трафик и улучшает пользовательский опыт. Постраничная загрузка позволяет приложению отображать контент постепенно, без ожидания полной загрузки всех данных.

Главное

  • Пагинация — способ загрузки больших наборов данных порциями для оптимизации производительности и трафика.
  • Offset-пагинация использует смещение (page/offset) для навигации — простой, но нестабильный метод при частых вставках.
  • Cursor-пагинация использует уникальный курсор из последней записи — стабильна при изменении данных между запросами.
  • Keyset-пагинация фильтрует по столбцу с уникальным индексом — эффективна для больших таблиц без дубликатов.
  • Time-based пагинация группирует записи по временным меткам — удобна для лент новостей и социальных сетей.

Что такое пагинация?

Пагинация (от англ. pagination — постраничное деление) — это техника разбиения большого набора данных на последовательные порции (страницы). В мобильных приложениях пагинация используется при загрузке списков сообщений, ленты новостей, каталогов товаров, истории заказов и любых других коллекций с потенциально неограниченным числом записей.

Без пагинации приложение вынуждено загружать все данные сразу, что приводит к долгому ожиданию, большому расходу трафика и нестабильной работе на слабых устройствах. API-запрос с пагинацией возвращает только одну порцию данных и мета-информацию для загрузки следующей — таким образом, приложение контролирует объём получаемой информации.

Основные метрики пагинации: размер страницы (page size) — количество записей на одной странице (обычно 10-50), и номер страницы или курсор — указатель на текущую позицию в наборе. Выбор размера страницы зависит от типа данных: для компактных элементов (названия) достаточно 20-30, для карточек с изображениями — 10-15.

Зачем нужна пагинация в мобильных приложениях

Мобильные устройства имеют ограниченные ресурсы: объём оперативной памяти, скорость процессора и лимиты трафика. Пагинация решает три ключевые задачи: снижение потребления памяти (в памяти хранятся только видимые элементы), ускорение первого отображения (первая порция грузится быстрее всего набора) и экономия трафика (данные подгружаются только когда пользователь прокручивает список).

Основные виды пагинации

Существует четыре основных вида пагинации, каждый из которых решает специфические задачи. Выбор метода зависит от требований к согласованности данных, архитектуры API, типа хранилища и допустимой сложности реализации на клиенте и сервере.

ТипПринцип работыСтабильностьСкорость на больших объёмах
OffsetLIMIT + OFFSET в SQLнизкаяпадает с ростом OFFSET
CursorWHERE id > last_idвысокаястабильная (O(log n))
KeysetWHERE key > last_keyвысокаястабильная (O(log n))
Time-basedWHERE created_at < last_timeсредняястабильная с индексом

Когда какой тип использовать

Offset-пагинация подходит для статических или редко обновляемых наборов данных, когда важна простая реализация. Cursor и Keyset — для динамических данных с частыми вставками. Time-based — для хронологических лент, где записи упорядочены по времени создания. GraphQL-стандарт Relay использует cursor-пагинацию как единственный рекомендуемый метод.

Offset-пагинация: преимущества и недостатки

Offset-пагинация — самый простой вид постраничной загрузки. Клиент передаёт параметры page и limit (или offset и limit), сервер применяет SQL OFFSET и LIMIT. Например, page=2, limit=20 вернёт записи с 21 по 40. Этот метод интуитивно понятен и легко реализуется на любом стеке.

python
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items")
async def get_items(
    page: int = Query(default=1, ge=1),
    limit: int = Query(default=20, le=100)
):
    offset = (page - 1) * limit
    items = await fetch_items(offset, limit)
    total = await count_items()
    return {
        "items": items,
        "total": total,
        "page": page,
        "pages": (total + limit - 1) // limit
    }

Проблема несогласованности данных

Главный недостаток Offset-пагинации — проблема пропущенных и дублирующихся записей. Если между двумя запросами в таблицу добавляются новые записи, OFFSET смещается: пользователь может увидеть одну запись дважды или пропустить новую. Это критично для лент новостей и чатов, где важна консистентность.

Другая проблема — падение производительности на больших OFFSET. База данных вынуждена сканировать и пропускать первые offset записей перед возвратом результата. При offset=100000 даже с LIMIT 20 сервер потратит заметное время на сканирование. PostgreSQL и MySQL демонстрируют линейное падение скорости с ростом OFFSET.

Когда Offset всё ещё хорош

Offset-пагинация остаётся лучшим выбором для: административных панелей (данные меняются редко, нужна навигация по страницам), отчётов и исторических логов (фиксированный срез данных), каталогов с фильтрацией (можно перейти на любую страницу). Также Offset проще всего реализовать на клиенте — RecyclerView с Paging 3 поддерживает его из коробки.

Keyset и Time-based пагинация

Keyset-пагинация использует уникальный ключ (обычно первичный ключ) для фильтрации записей. Вместо OFFSET запрос использует WHERE id > last_seen_id. Это обеспечивает стабильную производительность независимо от количества записей и отсутствие дубликатов при вставках, так как новые записи всегда имеют больший id.

sql
-- Offset-пагинация (проблемная)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;

-- Keyset-пагинация (стабильная)
SELECT * FROM posts
WHERE id > 100
ORDER BY id
LIMIT 20;

Time-based пагинация

Time-based пагинация (или cursor по времени) использует временную метку created_at для навигации. Клиент передаёт timestamp последней загруженной записи, сервер возвращает записи, созданные до или после этой метки. Метод популярен в социальных сетях и лентах новостей, где порядок записей определяется временем публикации.

Особенность Time-based пагинации — возможны дубликаты, если две записи созданы в одну миллисекунду. Для устранения этой проблемы комбинируют time-based ключ с уникальным id: WHERE (created_at, id) < (last_time, last_id). Такой составной курсор гарантирует уникальность каждой записи и точный порядок.

Сравнение Keyset и Time-based

Keyset-пагинация требует столбца с уникальным и монотонно возрастающим значением (автоинкрементный id, UUID v7). Time-based подходит для любых таблиц с created_at, но требует дополнительной обработки дубликатов. Основное различие: Keyset стабильно работает при любых операциях вставки, а Time-based чувствителен к одинаковым временным меткам.

Как выбрать тип пагинации для проекта

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

  • Чат / мессенджер — Cursor-пагинация (по id сообщения). Новые сообщения добавляются сверху, курсор не сбивается.
  • Лента новостей — Time-based пагинация (по created_at). Записи упорядочены по времени, важна хронология.
  • Каталог товаров — Offset-пагинация. Пользователь может перейти на конкретную страницу, данные меняются редко.
  • История заказов — Cursor-пагинация. Стабильность важна, так как новые заказы добавляются между загрузками.
  • Комментарии — Keyset-пагинация. Каждый комментарий имеет уникальный id, большие объёмы без дубликатов.

Реализация на Android с Paging 3

Библиотека Android Paging 3 поддерживает все типы пагинации через PagingSource. Для Offset — PagingSource с ключом Int (page), для Cursor — с ключом String или Long (cursor). PagingSource автоматически управляет загрузкой, кэшированием и повторными попытками при ошибках.

kotlin
class PostPagingSource(
    private val api: PostApi
) : PagingSource<Long, Post>() {

    override suspend fun load(
        params: LoadParams<Long>
    ): LoadResult<Long, Post> {
        val cursor = params.key ?: Long.MAX_VALUE
        return try {
            val response = api.getPosts(cursor, params.loadSize)
            LoadResult.Page(
                data = response.items,
                prevKey = null,
                nextKey = response.items.lastOrNull()?.id
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Long, Post>): Long? {
        return state.anchorPosition?.let {
            state.closestItemToPosition(it)?.id
        }
    }
}

Рекомендации по размеру страницы

Размер страницы влияет на скорость загрузки и восприятие производительности. Для мобильных приложений оптимальный диапазон — 10-25 элементов на страницу. Меньше 10 — слишком частые запросы к API и дёрганный скролл. Больше 25 — долгая загрузка первой порции на медленных сетях.

Для изображений и видео размер страницы уменьшают до 5-10, так как каждый элемент требует дополнительного времени на загрузку медиа. Для списков с текстовыми элементами (комментарии, логи) размер можно увеличить до 30-50 записей. Рекомендуется делать размер страницы настраиваемым через API, чтобы клиент мог адаптироваться под разные условия сети.

Часто задаваемые вопросы

Что такое пагинация простыми словами?

Пагинация — это загрузка данных частями, а не всеми сразу. Как в книге: вы читаете одну страницу, потом переворачиваете на следующую. В приложении это означает, что при прокрутке списка подгружается следующая порция данных, а не весь список целиком, что экономит трафик и память.

Чем Offset отличается от Cursor пагинации?

Offset считает записи: «пропусти 20, верни следующие 10». Если между загрузками добавилась новая запись — нумерация сбивается. Cursor использует уникальный идентификатор последней записи: «верни 10 записей после ID = 100». Новые записи не влияют на позицию.

Какой размер страницы пагинации оптимален?

Для мобильных приложений оптимально 10-25 элементов. Для списков с изображениями — 5-10, для текстовых лент — 20-30. Размер зависит от среднего размера одного элемента: чем тяжелее элемент, тем меньше должна быть страница для быстрого отображения.

Как реализовать пагинацию в RecyclerView?

Используйте библиотеку Paging 3 из Android Jetpack. Она предоставляет PagingSource для загрузки, PagingData для реактивного потока и PagingDataAdapter для автоматической подгрузки при прокрутке. Библиотека поддерживает Offset, Cursor и Keyset пагинацию через кастомный PagingSource.

Что такое бесконечный скролл и чем он отличается от пагинации?

Бесконечный скролл — это UI-паттерн, при котором новая порция данных загружается автоматически при приближении к концу списка. Пагинация — это механизм загрузки данных порциями, а бесконечный скролл — один из способов его отображения. Альтернатива — кнопка «Загрузить ещё».

Итоги

  • Пагинация — техника загрузки данных порциями, необходимая для мобильных приложений с любыми списками.
  • Offset-пагинация проста в реализации, но страдает от несогласованности при вставках и падения производительности на больших OFFSET.
  • Cursor-пагинация использует уникальный идентификатор для навигации — стабильна и эффективна на любых объёмах.
  • Keyset-пагинация фильтрует по первичному ключу, обеспечивая максимальную производительность за счёт использования индексов.
  • Time-based пагинация группирует записи по временным меткам — идеальна для хронологических лент и социальных сетей.
  • Выбор метода зависит от характера данных: для динамических — Cursor/Keyset, для статических — Offset, для лент — Time-based.
  • Paging 3 на Android и стандартные cursor-решения на iOS/вебе предоставляют готовую инфраструктуру для любого типа пагинации.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также