Offset Pagination в мобилното разработване: какво е това и как да го реализираме

Автор: IT Sectr Публикувано: 2026-03-11 Време за четене: 10 мин

Offset Pagination — пагинация с отместване — метод за постранично зареждане на данни чрез HTTP API. Клиентът предава параметрите offset (отместване от началото) и limit (размер на страницата), а сървърът връща записи от позицията offset. Според REST API Tutorial, този подход се използва широко в RESTful услугите поради простотата на реализация. Въпреки това, при големи обеми данни offset-пагинацията губи производителност поради пълното сканиране на таблицата до необходимата позиция.

Основни точки

  • Offset Pagination — метод за пагинация, при който сървърът пропуска N записа и връща следващите M.
  • Простотата на реализация го прави стандарт за REST API и мобилни клиенти.
  • Проблем с пропускането — при вмъкване на записи между заявките, потребителят вижда дубликати.
  • Скокове в данните — изтриването на записи води до отместване на страниците и загуба на съдържание.
  • Cursor-based пагинация решава тези проблеми чрез указател към последния запис вместо отместване.

Какво е Offset Pagination?

Offset Pagination — е метод за постранично разделяне на данни, при който клиентската заявка съдържа два параметъра: offset (колко записа да се пропуснат) и limit (колко записа да се върнат). Сървърът изпълнява SQL заявка с OFFSET и LIMIT, пропуска указания брой редове и връща резултатен набор с фиксиран размер.

Методът се появява в релационните бази данни като най-простия начин за организиране на навигация между страниците и е пренесен в HTTP API заедно с развитието на REST архитектурата. Offset Pagination не изисква съхранение на състояние на сървъра — всяка заявка е независима и съдържа цялата необходима информация за извличане.

Според изследване на API дизайна от Postman (2025), offset-пагинацията се използва в 72% от обществените REST API, което я прави доминиращ стандарт въпреки известните ограничения на производителността при големи обеми данни.

Структура на заявка и отговор

Типична REST заявка с Offset Pagination включва параметри на заявката offset и limit. Отговорът съдържа списък с записи от исканата страница и метаданни за изграждане на навигационния интерфейс.

Параметърът limit ограничава броя на връщаните записи и защитава сървъра и клиента от прекомерно натоварване. Типични стойности на limit — от 10 до 50 записа на страница в зависимост от сложността на данните.

kotlin
data class PageRequest(
    val offset: Int,
    val limit: Int
)

data class PageResponse<T>(
    val items: List<T>,
    val total: Int,
    val hasMore: Boolean
)

fun RetrofitApi.fetchPage(request: PageRequest): Call<PageResponse<Item>>

Как работи Offset Pagination

Offset Pagination се преобразува в SQL заявка с конструкциите OFFSET и FETCH NEXT (или LIMIT в MySQL/SQLite). Сървърът на базата данни сканира таблицата, пропуска броя редове, равен на offset, и връща следващите limit реда. Колкото по-голям е offset, толкова по-дълго трае заявката.

Проблемът с производителността е свързан с факта, че базата данни не може да премине директно до позицията offset — тя трябва да прочете и изхвърли всички предишни редове. При offset = 100000 и limit = 20, СУБД ще прочете 100020 реда и ще върне само 20.

SQL заявка под капака

SQL — езикът, на който сървърът изпълнява offset-пагинация. В PostgreSQL и MySQL се използва LIMIT, в SQL Server и Oracle — OFFSET...FETCH. Различни СУБД оптимизират тази заявка по свой начин, но фундаменталният проблем със сканирането остава същият.

sql
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;

Проблем с консистентността

Консистентност на данните — основният недостатък на Offset Pagination при работа с динамични набори. Ако между две потребителски заявки се добави нов запис в началото на таблицата, всички съществуващи записи се изместват. Потребителят вижда дубликати или пропуски.

Представете си таблица със 100 записа с limit = 20. На страница 1 потребителят вижда записи 1-20. Администраторът добавя 5 нови записа. На страница 2 потребителят вижда записи 26-45 вместо очакваните 21-40 — записи 21-25 са пропуснати, а записи 21-25 от предишния набор са дублирани на страница 1.

Offset vs Cursor-based: сравнение на подходите

Cursor-based pagination — алтернатива на Offset Pagination, която използва указател към последния запис на текущата страница. Вместо числово отместване, клиентът предава идентификатора на последния получен запис, а сървърът връща следващите N записа след него.

Cursor-based подходът решава проблема с консистентността: позицията на курсора не се променя при вмъквания или изтривания, тъй като курсорът сочи към конкретен запис, а не към позиция. Въпреки това, той е по-сложен за реализация — изисква уникално сортируемо поле (обикновено ID или timestamp).

ПараметърOffset PaginationCursor-based Pagination
ПростотаВисока — два числови параметъраСредна — изисква кодиране на курсора
КонсистентностНиска — дубликати при вмъкванеВисока — курсорът не зависи от промени
ПроизводителностНамалява с растежа на offsetСтабилна при всякакъв обем
Скок на страницаДа — може да се отиде на всяка страницаНе — само последователна навигация
Подходящо заТаблици <10K записа, UI с номера на странициПотоци, безкраен скрол, големи набори

Изборът между подходите зависи от изискванията за интерфейса на потребителя. Ако е необходима навигация с номера на страници и директен скок — Offset Pagination е по-прост. За безкраен скрол или новинарски потоци, курсорите са за предпочитане.

Keyset pagination

Keyset pagination — разновидност на cursor-based подхода, при която филтрирането се извършва по уникален ключ с WHERE вместо OFFSET. SQL заявката използва условието WHERE id > lastId, което позволява на базата данни да използва индекс без сканиране на изхвърлените редове.

Според PostgreSQL Wiki, keyset pagination се изпълнява 100-1000 пъти по-бързо от offset заявка при големи отмествания, тъй като индексното сканиране замества пълното обхождане на таблицата. Недостатък — невъзможност за скок на произволна страница без последователно преминаване.

Кога да използваме Offset Pagination

Offset Pagination е оптимална за малки и средни набори от данни (до 10000 записа), където потребителят се нуждае от интерфейс с номера на страници. Типични сценарии — административни панели, списъци с поръчки, каталози с филтриране и пагинация по страници.

За мобилни приложения, offset-пагинацията е подходяща при зареждане на исторически данни, където вмъкването на нови записи е рядко или невъзможно — например история на поръчките на потребителя, списък на завършени задачи, архив на транзакции. В тези сценарии проблемът с консистентността не възниква.

Не се препоръчва използването на Offset Pagination за потоци от социални мрежи, списъци с коментари, чатове и други динамични набори с чести вмъквания. В тези случаи, пропуските и дубликатите на записи влошават потребителското изживяване и изискват допълнителна логика за дедупликация на клиента.

Хибриден подход

Хибридна пагинация комбинира offset и курсор: първата заявка използва offset за показване на началната страница, а следващите — курсор за зареждане на безкраен скрол. Този подход е реализиран в Instagram и Twitter, където първата страница се зарежда чрез курсор, но offset се използва за изчисляване на позицията при връщане към предишния изглед.

Реализацията на хибридния подход изисква съхранение на виртуалната позиция на потребителя на клиента и координиране на два механизма за пагинация на сървъра. Според блога на Instagram Engineering, техният екип използва cursor-based пагинация с допълнително поле startCursor, което замества offset за начално зареждане.

Offset Pagination в мобилни приложения

Мобилни приложения използват Offset Pagination заедно с Retrofit/OkHttp на Android и URLSession/Combine на iOS. Типичният модел — зареждане на следващата страница при скрол до края на списъка чрез RecyclerView.OnScrollListener или UICollectionView prefetching.

Реализацията на offset-пагинация на мобилен клиент включва три компонента: мениджър на пагинацията (съхранява текущия offset и hasMore), адаптер на списъка (показва елементите и индикатора за зареждане) и хранилище (изпълнява заявки и обработва грешки). Android Jetpack предлага Paging 3 Library, която поддържа както offset, така и cursor-based пагинация направо от кутията.

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

Paging 3 — библиотека на Android Jetpack за постранично зареждане на данни. Тя капсулова логиката на пагинация, включително проследяване на offset, управление на състоянието на зареждане и автоматично зареждане при скрол. PagingSource дефинира ключове за следващата и предишната страница.

kotlin
class OffsetPagingSource(
    private val api: ApiService,
    private val limit: Int = 20
) : PagingSource<Int, Item>() {

    override suspend fun load(
        params: LoadParams<Int>
    ): LoadResult<Int, Item> {
        val offset = params.key ?: 0
        return try {
            val response = api.getItems(offset, limit)
            LoadResult.Page(
                data = response.items,
                prevKey = null,
                nextKey = if (response.hasMore) offset + limit else null
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

PagingSource дефинира ключове prevKey и nextKey за навигация по страници. При offset-пагинация, prevKey винаги е null (не може да се отиде на предишната страница без запазване на история), а nextKey се увеличава с limit при всяко зареждане, докато сървърът не върне hasMore = false. Това е прост и предвидим модел за мобилни списъци.

Типични грешки при Offset Pagination

Първа грешка — разчитане на реда на записите без сортиране. Offset Pagination изисква стабилно сортиране ORDER BY по уникално поле. Без него СУБД може да връща записи в произволен ред, което води до случайни дубликати и пропуски между страниците.

Втора грешка — използване на offset за изчисляване на номера на страница в интерфейса. Формулата page = offset / limit + 1 работи само при условие, че нито един запис не е бил изтрит или добавен между зарежданията. При динамични данни номерът на страницата става неточен и потребителят вижда некоректна информация.

Трета грешка — игнориране на таймаутите на заявки с голям offset. При offset над 100000, заявката може да отнеме десетки секунди, блокирайки интерфейса и изразходвайки ресурси на сървъра. Препоръчително е да се зададе максимална стойност на offset на ниво API (например 10000) и да се използва cursor-based пагинация за големи обеми.

Четвърта грешка — недобавяне на total count в отговора. Без общия брой записи, клиентът не може да покаже броя на страниците и да реализира пагинация с номера. Въпреки това, изчисляването на COUNT(*) на големи таблици също е скъпо — за набори над 100000 записа използвайте приблизителни оценки или ограничете максималната стойност на total.

Често задавани въпроси

По какво Offset Pagination се различава от Cursor-based?

Offset използва числово отместване (offset) за пропускане на записи, а курсорът — указател към последния запис на предишната страница. Offset е по-прост за реализация, но страда от дубликати при вмъкване и загуба на производителност при големи отмествания. Курсорът е стабилен при всякакви промени в данните.

Кога Offset Pagination работи лошо?

Offset пагинацията е неефективна при offset над 10000 записа поради пълно сканиране на таблицата. Също така е неподходяща за динамични набори (потоци, чатове), където нови записи се появяват между заявките — потребителят вижда пропуски и дубликати при навигация.

Какъв limit е оптимален за Offset Pagination?

Оптималният limit зависи от размера на записа и скоростта на мрежата — от 10 до 50 елемента на страница. За списъци с големи изображения използвайте limit = 10-15, за текстови данни — 20-50. Винаги позволявайте на клиента да посочи свой limit с максимално ограничение на сървъра (обикновено 100).

Как да се борим с дубликатите при Offset Pagination?

За борба с дубликатите използвайте дедупликация на клиента по уникален ID, приложете стабилно сортиране по уникално поле или преминете на cursor-based пагинация. Android Paging 3 поддържа ключ за автоматична дедупликация на елементи от списъка.

Може ли Offset Pagination да се използва с GraphQL?

Да, GraphQL поддържа offset-пагинация чрез аргументите offset и limit в заявката, въпреки че спецификацията Relay препоръчва cursor-based подход. Библиотеките Apollo GraphQL и Relay предлагат вградена поддръжка за offset-пагинация с автоматично управление на състоянието на страниците.

Резюме

  • Offset Pagination — метод за пагинация с параметри offset и limit за пропускане и ограничаване на записи при постранично зареждане на данни от API.
  • Простотата на реализация и независимостта на заявките правят Offset Pagination стандартния подход за 72% от REST API (данни на Postman, 2025).
  • Производителността намалява при offset над 10000 поради сканиране на таблицата до необходимата позиция — базата данни чете всички изхвърлени редове.
  • Проблем с консистентността — вмъкването и изтриването на записи между заявките води до дубликати и пропуски в резултатите от страниците.
  • Cursor-based пагинация решава проблемите на Offset Pagination чрез указател към последния запис вместо числово отместване.
  • Хибридният подход комбинира offset на първата страница с курсорно зареждане за безкраен скрол в мобилни приложения.
  • Препоръка — използвайте Offset Pagination за статични набори до 10000 записа и преминете на курсори за големи обеми и динамични данни.

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

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също