Offset Pagination — stránkování s posunem — metoda postupného načítání dat přes HTTP API. Klient předává parametry offset (posun od začátku) a limit (velikost stránky) a server vrací záznamy od pozice offset. Podle REST API Tutorial je tento přístup široce používán v RESTful službách díky jednoduchosti implementace. Při velkých objemech dat však offsetové stránkování ztrácí výkon kvůli úplnému skenování tabulky až do požadované pozice.
Hlavní body
Offset Pagination — je metoda rozdělení dat na stránky, při které požadavek klienta obsahuje dva parametry: offset (kolik záznamů přeskočit) a limit (kolik záznamů vrátit). Server provede SQL dotaz s OFFSET a LIMIT, přeskočí zadaný počet řádků a vrátí sadu výsledků pevné velikosti.
Metoda se objevila v relačních databázích jako nejjednodušší způsob organizace navigace mezi stránkami a byla přenesena do HTTP API s rozvojem REST architektury. Offset Pagination nevyžaduje ukládání stavu na serveru — každý požadavek je nezávislý a obsahuje všechny informace potřebné pro výběr.
Podle výzkumu návrhu API od Postman (2025) se offsetové stránkování používá v 72% veřejných REST API, což z něj činí dominantní standard navzdory známým omezením výkonu při velkých objemech dat.
Typický REST požadavek s Offset Pagination obsahuje parametry dotazu offset a limit. Odpověď obsahuje seznam záznamů požadované stránky a metadata pro vytvoření navigačního rozhraní.
Parametr limit omezuje počet vrácených záznamů a chrání server a klienta před nadměrným zatížením. Typické hodnoty limit — od 10 do 50 záznamů na stránku v závislosti na složitosti dat.
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 se převádí na SQL dotaz s konstrukcemi OFFSET a FETCH NEXT (nebo LIMIT v MySQL/SQLite). Databázový server skenuje tabulku, přeskočí počet řádků rovný offset a vrátí následujících limit řádků. Čím větší je offset, tím déle dotaz trvá.
Problém výkonu souvisí s tím, že databáze nemůže přejít přímo na pozici offset — musí přečíst a zahodit všechny předchozí řádky. Při offset = 100000 a limit = 20 DBMS přečte 100020 řádků a vrátí pouze 20.
SQL — jazyk, ve kterém server provádí offsetové stránkování. V PostgreSQL a MySQL se používá LIMIT, v SQL Server a Oracle — OFFSET...FETCH. Různé DBMS optimalizují tento dotaz po svém, ale základní problém skenování zůstává stejný.
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;
Konzistence dat — hlavní nevýhoda Offset Pagination při práci s dynamickými sadami. Pokud se mezi dvěma požadavky uživatele přidá nový záznam na začátek tabulky, všechny existující záznamy se posunou. Uživatel vidí duplicity nebo přeskočení.
Představte si tabulku se 100 záznamy s limit = 20. Na stránce 1 uživatel vidí záznamy 1-20. Správce přidá 5 nových záznamů. Na stránce 2 uživatel vidí záznamy 26-45 místo očekávaných 21-40 — záznamy 21-25 jsou přeskočeny a záznamy 21-25 z předchozí sady jsou zdvojeny na stránce 1.
Cursor-based pagination — alternativa k Offset Pagination, která používá ukazatel na poslední záznam aktuální stránky. Místo číselného posunu klient předává identifikátor posledního přijatého záznamu a server vrátí následujících N záznamů po něm.
Cursor-based přístup řeší problém konzistence: pozice kurzoru se nemění při vkládání nebo mazání, protože kurzor odkazuje na konkrétní záznam, nikoli na pozici. Je však složitější na implementaci — vyžaduje jedinečné řaditelné pole (obvykle ID nebo timestamp).
| Parametr | Offset Pagination | Cursor-based Pagination |
|---|---|---|
| Jednoduchost | Vysoká — dva číselné parametry | Střední — vyžaduje kódování kurzoru |
| Konzistence | Nízká — duplicity při vkládání | Vysoká — kurzor nezávisí na změnách |
| Výkon | Klesá s růstem offset | Stabilní při jakémkoli objemu |
| Skok na stránku | Ano — lze přejít na libovolnou stránku | Ne — pouze sekvenční navigace |
| Vhodné pro | Tabulky <10K záznamů, UI s čísly stránek | Feed, nekonečný scroll, velké sady |
Výběr mezi přístupy závisí na požadavcích na rozhraní uživatele. Pokud je potřeba navigace s čísly stránek a přímým skokem — Offset Pagination je jednodušší. Pro nekonečný scroll nebo zpravodajské feedy jsou vhodnější kurzory.
Keyset pagination — varianta cursor-based přístupu, při které se filtrování provádí pomocí jedinečného klíče s WHERE namísto OFFSET. SQL dotaz používá podmínku WHERE id > lastId, což umožňuje databázi použít index bez skenování zahozených řádků.
Podle PostgreSQL Wiki se keyset pagination provádí 100-1000krát rychleji než offsetový dotaz při velkých posunech, protože indexové skenování nahrazuje úplný průchod tabulky. Nevýhoda — nemožnost skoku na libovolnou stránku bez sekvenčního průchodu.
Offset Pagination je optimální pro malé a střední datové sady (do 10000 záznamů), kde uživatel potřebuje rozhraní s čísly stránek. Typické scénáře — admin panely, seznamy objednávek, katalogy s filtrováním a stránkováním po stránkách.
Pro mobilní aplikace je offsetové stránkování vhodné při načítání historických dat, kde je vkládání nových záznamů vzácné nebo nemožné — například historie objednávek uživatele, seznam dokončených úkolů, archiv transakcí. V těchto scénářích problém konzistence nenastává.
Nedoporučuje se používat Offset Pagination pro feedy sociálních sítí, seznamy komentářů, chaty a další dynamické sady s častým vkládáním. V těchto případech přeskočení a duplicity záznamů zhoršují uživatelský zážitek a vyžadují dodatečnou logiku deduplikace na klientovi.
Hybridní stránkování kombinuje offset a kurzor: první požadavek používá offset pro zobrazení úvodní stránky a následující používají kurzor pro načítání nekonečného scrollu. Tento přístup je implementován v Instagramu a Twitteru, kde se první stránka načítá přes kurzor, ale offset se používá pro výpočet pozice při návratu k předchozímu zobrazení.
Implementace hybridního přístupu vyžaduje ukládání virtuální pozice uživatele na klientovi a koordinaci dvou mechanismů stránkování na serveru. Podle blogu Instagram Engineering jejich tým používá cursor-based stránkování s dodatečným polem startCursor, které nahrazuje offset pro počáteční načítání.
Mobilní aplikace používají Offset Pagination v kombinaci s Retrofit/OkHttp na Androidu a URLSession/Combine na iOS. Typický vzor — načítání další stránky při scrollování na konec seznamu přes RecyclerView.OnScrollListener nebo UICollectionView prefetching.
Implementace offsetového stránkování na mobilním klientovi zahrnuje tři komponenty: správce stránkování (ukládá aktuální offset a hasMore), adaptér seznamu (zobrazuje prvky a indikátor načítání) a repozitář (provádí požadavky a zpracovává chyby). Android Jetpack nabízí knihovnu Paging 3, která podporuje jak offsetové, tak cursor-based stránkování po vybalení.
Paging 3 — knihovna Android Jetpack pro postupné načítání dat. Zapouzdřuje logiku stránkování, včetně sledování offset, správy stavu načítání a automatického načítání při scrollování. PagingSource definuje klíče pro následující a předchozí stránku.
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 definuje klíče prevKey a nextKey pro navigaci po stránkách. Při offsetovém stránkování je prevKey vždy null (nelze přejít na předchozí stránku bez uložení historie) a nextKey se při každém načítání zvyšuje o limit, dokud server nevrátí hasMore = false. To je jednoduchý a předvídatelný model pro mobilní seznamy.
První chyba — spoléhání na pořadí záznamů bez řazení. Offset Pagination vyžaduje stabilní řazení ORDER BY podle jedinečného pole. Bez něj může DBMS vracet záznamy v libovolném pořadí, což vede k náhodným duplicitám a přeskočením mezi stránkami.
Druhá chyba — použití offset pro výpočet čísla stránky v rozhraní. Vzorec page = offset / limit + 1 funguje pouze za podmínky, že žádný záznam nebyl mezi načítáními smazán nebo přidán. Při dynamických datech se číslo stránky stává nepřesným a uživatel vidí nesprávné informace.
Třetí chyba — ignorování časových limitů dotazů s velkým offset. Při offset nad 100000 může dotaz trvat desítky sekund, blokovat rozhraní a spotřebovávat prostředky serveru. Doporučuje se nastavit maximální hodnotu offset na úrovni API (například 10000) a používat cursor-based stránkování pro velké objemy.
Čtvrtá chyba — nepřidávání total count do odpovědi. Bez celkového počtu záznamů klient nemůže zobrazit počet stránek a implementovat stránkování s čísly. Výpočet COUNT(*) na velkých tabulkách je však také nákladný — pro sady nad 100000 záznamů používejte přibližné odhady nebo omezte maximální hodnotu total.
Často kladené otázky
Offset používá číselný posun (offset) pro přeskočení záznamů, zatímco kurzor používá ukazatel na poslední záznam předchozí stránky. Offset je jednodušší na implementaci, ale trpí duplicitami při vkládání a ztrátou výkonu při velkých posunech. Kurzor je stabilní při jakýchkoli změnách dat.
Offsetové stránkování je neefektivní při offset nad 10000 záznamů kvůli úplnému skenování tabulky. Je také nevhodné pro dynamické sady (feed, chaty), kde se mezi požadavky objevují nové záznamy — uživatel vidí přeskočení a duplicity při navigaci.
Optimální limit závisí na velikosti záznamu a rychlosti sítě — od 10 do 50 prvků na stránku. Pro seznamy s velkými obrázky použijte limit = 10-15, pro textová data — 20-50. Vždy povolte klientovi zadat vlastní limit s maximálním omezením na serveru (obvykle 100).
Pro boj s duplicitami použijte deduplikaci na klientovi podle jedinečného ID, aplikujte stabilní řazení podle jedinečného pole nebo přejděte na cursor-based stránkování. Android Paging 3 podporuje klíč pro automatickou deduplikaci prvků seznamu.
Ano, GraphQL podporuje offsetové stránkování přes argumenty offset a limit v dotazu, ačkoli specifikace Relay doporučuje cursor-based přístup. Knihovny Apollo GraphQL a Relay nabízejí vestavěnou podporu pro offsetové stránkování s automatickou správou stavu stránek.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také