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 са бројевима страницаFeed-ове, бесконачни скрол, велике скупове

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

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-а за feed-ове друштвених мрежа, листе коментара, четове и друге динамичке скупове са честим уметањима. У овим случајевима, прескакања и дупликати записа погоршавају корисничко искуство и захтевају додатну логику дедупликације на клијенту.

Хибридни приступ

Хибридна пагинација комбинује offset и cursor: први захтев користи offset за приказ почетне странице, а наредни — cursor за учитавање бесконачног скрола. Овај приступ је имплементиран у Instagram-у и Twitter-у, где се прва страница учитава преко cursor-а, али се 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 пагинације за велике обимe.

Четврта грешка — недодавање total count у одговор. Без укупног броја записа, клијент не може да прикаже број страница и имплементира пагинацију са бројевима. Међутим, израчунавање COUNT(*) на великим табелама је такође скупо — за скупове преко 100000 записа користите приближне процене или ограничите максималну вредност total.

Често постављана питања

По чему се Offset Pagination разликује од Cursor-based?

Offset користи нумерички померај (offset) за прескакање записа, а cursor — показивач на последњи запис претходне странице. Offset је једноставнији за имплементацију, али пати од дупликата при уметањима и губитка перформанси при великим померајима. Cursor је стабилан при било каквим променама података.

Када Offset Pagination ради лоше?

Offset пагинација је неефикасна при offset-у преко 10000 записа због потпуног скенирања табеле. Такође је неприкладна за динамичке скупове (feed-ови, четови) где се нови записи појављују између захтева — корисник види прескакања и дупликате записа при навигацији.

Који 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 прве странице са cursor учитавањем за бесконачни скрол у мобилним апликацијама.
  • Препорука — користите Offset Pagination за статичке скупове до 10000 записа и пређите на курсоре за велике обимe и динамичке податке.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође