Cursor Pagination a mobilfejlesztésben — mi ez, elv és megvalósítás

Szerző: IT Sectr Megjelenés: 2026-03-11 Olvasási idő: 9 perc

Cursor Pagination — adatok lapozásának módszere, amely egy egyedi kurzort használ a rendezett rekordhalmazban való navigáláshoz. A GraphQL Specification (2025) szerint a kurzoros lapozás az ajánlott szabvány a dinamikus adatokkal dolgozó API-k számára. Cursor Pagination kiküszöböli az Offset-megközelítés fő hátrányait: instabilitást beszúráskor és teljesítménycsökkenést nagy eltolásoknál.

Főbb pontok

  • Cursor Pagination — lapozási módszer, ahol minden rekord egyedi azonosító-kurzorral rendelkezik a navigációhoz.
  • Kurzor — egyedi pozíciójelző az adathalmazban (általában ID, UUID, timestamp), amely nem változik beszúráskor.
  • Stabilitás — a kérések között hozzáadott új rekordok nem tolják el a kurzort, kiküszöbölve a duplikátumokat és kihagyásokat.
  • Teljesítmény — a WHERE id > cursor lekérdezés hatékonyan használja az indexet, nem veszítve sebességből nagy halmazokon.
  • Korlátozás — a kurzoros lapozás nem támogatja az oldalszám szerinti navigációt (nem lehet az 5. oldalra ugrani).

Mi az a Cursor Pagination?

Cursor Pagination (kurzoros lapozás) — lapozási módszer, ahol a szerver az adatokkal együtt egy speciális mutatót — kurzort — ad vissza. A kliens ezt a kurzort használja a következő kérésben a rekordok következő adagjának lekéréséhez. A kurzor az aktuális oldal utolsó elemének egyedi azonosítója.

Az Offset-lapozástól eltérően, ahol a kliens azt mondja „add az 5. oldalt 20 rekorddal”, a kurzoros lapozás másképp működik: „add meg a 20 rekordot az ID = 83 rekord után”. A szerver végrehajtja a lekérdezést a WHERE id > 83 feltétellel és LIMIT 20-szal. Ez a megközelítés garantálja, hogy minden rekord pontosan egy oldalra kerül, függetlenül a beszúrásoktól.

A kurzoros lapozás koncepciója széles körben elterjedt a Relay Connection (GraphQL) specifikációnak köszönhetően, amely a cursor-based pagination-t a modern API-k szabványává tette. A Relay meghatározza a válasz formátumát: edges (rekordok tömbje kurzorokkal), pageInfo (hasNextPage, hasPreviousPage, startCursor, endCursor).

A keletkezés története

A cursor-pagination nem új technika — már jóval a web megjelenése előtt használták adatbázisokban. Az SQL-ben keyset pagination vagy seek method néven ismert. A módszer az API-kban a Relay specifikáció 2015-ös közzététele után vált népszerűvé, amely formalizálta a kurzor formátumát base64-kódolt sztringként az HTTP-n keresztüli egységes átvitel érdekében.

Hogyan működik a kurzoros lapozás

A kurzoros lapozás alapelve — a lekérdezés WHERE feltételt használ egy indexelt mezőn a pozicionáláshoz, nem eltolást. Előre irányhoz WHERE id > last_id, hátra irányhoz WHERE id < first_id alkalmazandó. A B-tree index lehetővé teszi az első rekord megtalálását a kurzor után O(log n) idő alatt, ami stabil válaszidőt biztosít.

sql
-- 20 rekord lekérése a '83' kurzor után
SELECT id, title, created_at
FROM posts
WHERE id < 83
ORDER BY id DESC
LIMIT 20;

-- 20 rekord lekérése a '83' kurzor ELŐTT (visszafelé)
SELECT id, title, created_at
FROM posts
WHERE id > 83
ORDER BY id ASC
LIMIT 20;

A kurzor formátuma

A kurzor lehet egyszerű (ID érték) vagy összetett (több mezőből álló). Az egyszerű kurzorok a rekord elsődleges kulcsai, például auto-increment id vagy UUID. Az összetett kurzorokat nem egyedi mezők szerinti rendezéshez használják, például (created_at, id), ahol az id garantálja az egyediséget azonos időbélyegek esetén.

Tipikus API formátum — a kurzor base64-kódolt sztring formájában. A szerver dekódolja a kurzort, kinyeri az értéket és felépíti az SQL lekérdezést. A Base64-kódolás elrejti a kurzor belső struktúráját a kliens elől, és lehetővé teszi a formátum megváltoztatását a visszafelé kompatibilitás elvesztése nélkül. A kliens a kurzorokat a válasz endCursor mezőjében kapja, és sztringként adja tovább a következő kérésben.

Navigáció előre és hátra

A kurzoros lapozás támogatja a kétirányú navigációt. Az előre haladáshoz (next) az aktuális oldal utolsó elemének kurzorát, hátra (previous) — az első elem kurzorát használjuk. A kérés after és before paraméterei határozzák meg az irányt: az after a kurzor utáni rekordokat veszi, a before — a kurzor előttieket.

Cursor vs Offset pagination

A kurzoros és az Offset-lapozás közötti választás az API tervezésének egyik kulcsfontosságú architekturális kérdése. Minden módszernek vannak erős és gyenge oldalai. Cursor-pagination a dinamikus adatokkal végzett forgatókönyvekben nyer, az Offset — a tetszőleges navigációt igénylő forgatókönyvekben.

JellemzőCursorOffset
Stabilitás beszúráskorMagas (nincs duplikátum)Alacsony (oldaleltolódás)
Teljesítmény nagy halmazokonO(log n) — stabilO(n) — csökken a növekedéssel
Navigáció oldalszám szerintNemIgen (page=5)
Megvalósítás bonyolultságaKözepesAlacsony
Támogatás REST-bencursor/before/afterpage/offset
Támogatás GraphQL-benRelay szabványNem ajánlott

Miért veszít az Offset nagy léptékben

Az Offset-lapozás teljes táblabeolvasást végez az OFFSET pozícióig. Offset=100000 esetén az adatbázis 100000 sort olvas és hagy ki, még akkor is, ha a LIMIT 20. A MySQL és PostgreSQL nem tudja optimalizálni az OFFSET-et — ez a LIMIT/OFFSET SQL-beli megvalósításának jellemzője. A Cursor-pagination a B-tree indexet használja, amely O(log n) idő alatt találja meg a pozíciót.

Az Offset további problémája — rekordok „kihagyása” visszafelé lapozáskor. Ha a felhasználó betöltötte az 5. oldalt, és ekkor új rekordok kerültek hozzáadásra, a 6. oldal kérésekor az 5. oldal rekordját fogja újra látni vagy kihagyja az újakat. A Cursor-pagination teljesen kiküszöböli ezt a forgatókönyvet: a kurzor egy konkrét helyre mutat a halmazban, és a beszúrások nem változtatják meg a pozíciót.

A Cursor Pagination megvalósítása

Tekintsük át a kurzoros lapozás megvalósítását a backend-en (Kotlin + Spring) és a kliensen (Android + Retrofit). A szerver fogadja az after, before, limit paramétereket, és visszaadja a rekordok listáját kurzorokkal és pageInfo-val. A tipikus válasz tartalmazza a hasNextPage és hasPreviousPage mezőket a lapozási UI kezeléséhez.

kotlin
@GetMapping("/posts")
fun getPosts(
    @RequestParam after: Long?,
    @RequestParam(defaultValue = "20") limit: Int
): CursorResponse<Post> {
    val cursor = after ?: Long.MAX_VALUE
    val posts = repository.findByIdLessThanOrderByIdDesc(
        cursor, PageRequest.of(0, limit)
    )
    val endCursor = posts.lastOrNull()?.id
    return CursorResponse(
        data = posts,
        pageInfo = PageInfo(
            hasNextPage = posts.size == limit,
            endCursor = endCursor
        )
    )
}

Kliens oldali megvalósítás Androidon

A kliens oldalon a kurzoros lapozás a Paging 3 PagingSource-ján keresztül valósul meg, ahol a kulcs a kurzor (Long). A PagingSource.load megkapja a LoadParams.key — az utolsó betöltött rekord kurzorát. LoadResult.Page visszaadja az adatokat és a nextKey — a következő oldal kurzorát. Amikor a nextKey = null — a lapozás befejeződött.

kotlin
// Retrofit API
interface PostApi {
    @GET("posts")
    suspend fun getPosts(
        @Query("after") after: Long?,
        @Query("limit") limit: Int = 20
    ): CursorResponse<Post>
}

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

    override suspend fun load(
        params: LoadParams<Long>
    ): LoadResult<Long, Post> = try {
        val response = api.getPosts(
            after = params.key,
            limit = params.loadSize
        )
        val nextKey = response.pageInfo.endCursor
        LoadResult.Page(
            data = response.data,
            prevKey = null,
            nextKey = nextKey
        )
    } catch (e: Exception) {
        LoadResult.Error(e)
    }
}

GraphQL megvalósítás Relay-en keresztül

GraphQL-ben a kurzoros lapozás a Relay Connection minta segítségével valósul meg. Minden típus rendelkezik Connection-nel (pageInfo és edges) és Edge-dzsel (node + cursor). A kérés átadja a first, after, last, before paramétereket. A szerver visszaad egy edges tömböt kurzorokkal és pageInfo-t hasNextPage/hasPreviousPage mezőkkel.

Mikor használjuk a Cursor Pagination-t

A cursor-pagination olyan API-k számára ajánlott, amelyek dinamikus adatokkal dolgoznak, ahol a rekordok gyakran kerülnek hozzáadásra vagy törlésre. Klasszikus példák: hírfolyam közösségi hálózaton, chat üzenetek, tranzakciók előzményei, hozzászólások. Mindezen forgatókönyvekben fontos a konzisztencia és a duplikátumok hiánya.

  • Chatek és üzenetküldők — minden új üzenet a lista tetejére kerül. Az Offset-lapozás minden új üzenetnél elromlik.
  • Közösségi hálózatok és hírfolyamok — a bejegyzések folyamatosan jelennek meg. A kurzoros lapozás garantálja, hogy a felhasználó nem hagy ki egyetlen bejegyzést sem.
  • Rendelések és tranzakciók előzményei — az adatok ritkábban változnak, de a konzisztencia kritikus a pénzügyi jelentésekhez.
  • Nagy adatmennyiséggel dolgozó API-k — milliós rekordok. A Cursor-pagination megtartja a teljesítményt ott, ahol az Offset lassulni kezd.
  • GraphQL API — a Relay szabvány előírja a cursor-based pagination használatát a specifikációnak megfelelően.
  • Végtelen görgetésű mobilalkalmazások — a felhasználó lefelé görget, új adagokat töltve be. A kurzoros megközelítés gördülékeny UX-t biztosít duplikátumok nélkül.

Mikor nem megfelelő a Cursor-pagination

Vannak forgatókönyvek, ahol az Offset-lapozás kényelmesebb: adminisztrációs panelek, ahol oldalszám szerinti navigáció szükséges; keresés lapozással, ahol az eredmények változhatnak; jelentések és analitika, ahol fix hivatkozás kell az 5. oldalra. Ezekben az esetekben a kurzor előnyei nem haladják meg a megvalósítás bonyolultságát.

A kurzoros lapozás nem támogatja az „ugrást” tetszőleges oldalra — a felhasználó nem kattinthat az „5. oldal” gombra, hogy odajusson. Ez architekturális korlátozás: az oldalak teljes számának kiszámításához külön COUNT lekérdezés szükséges, ami nagy táblák esetén költséges lehet. Ilyen esetekben hibrid megközelítés: cursor az adatokhoz + count a lapozáshoz.

Gyakran ismételt kérdések

Mi az a kurzor a Cursor Pagination-ben?

A kurzor a rekord egyedi azonosítója, amely az adathalmazban elfoglalt pozíciót jelzi. Lehet egyszerű (rekord ID) vagy összetett (több mező). A kliens megkapja az oldal utolsó rekordjának kurzorát, és továbbadja a következő kérésben a következő adag lekéréséhez.

Miben jobb a Cursor Pagination az Offset-nél?

A cursor-pagination nincs kitéve eltolódásnak új rekordok hozzáadásakor — minden elem pontosan egy oldalra kerül. Emellett megtartja a sebességet nagy mennyiségek esetén is az indexek használatának köszönhetően, ahelyett hogy az első n sort szkennelné. Az Offset egyszerűbb, de instabil a dinamikus adatok számára.

Megvalósítható a kurzoros lapozás GraphQL nélkül?

Igen, a kurzoros lapozás nem kötődik a GraphQL-hez. Bármely REST API-ban megvalósítható a kurzor lekérdezési paraméterként való átadásával ?after=83&limit=20. A válasznak tartalmaznia kell a pageInfo-t endCursor és hasNextPage mezőkkel — ez lehetővé teszi a kliens számára a betöltés kezelését a kurzor belső struktúrájának ismerete nélkül.

Melyik kurzort használjuk — ID, UUID vagy timestamp?

Auto-increment ID — az optimális választás: monoton növekszik, nem változik, hatékonyan indexelt. Az UUID v7 (idő szerint rendezett) szintén megfelelő. A timestamp duplikátumokat adhat azonos időben, ezért ID-val kombináljuk: (created_at, id) a kurzor egyediségének garantálásához.

Hogyan tudjuk meg az oldalak teljes számát kurzoros lapozásnál?

A kurzoros lapozás nem adja meg az oldalak teljes számát — ez a korlátozása. Ha szükség van a total információra, hajtson végre egy külön COUNT lekérdezést ugyanazokkal a szűrőkkel. Nagy táblák esetén használjon közelítő számolást EXPLAIN segítségével vagy gyorsítótárazott total-t az analitikából.

Összefoglalás

  • Cursor Pagination — lapozási módszer navigációval a rekord egyedi azonosítója alapján, eltolás helyett.
  • Kurzor garantálja a halmaz stabilitását beszúráskor: az új rekordok nem tolják el a már betöltött oldalakat.
  • Teljesítmény nagy adatmennyiségek esetén is magas marad (O(log n)) a B-tree indexek használatának köszönhetően.
  • Cursor-pagination dinamikus adatokhoz alkalmas: chatek, hírfolyamok, tranzakciók, hozzászólások.
  • Fő korlátozás — az oldalszám szerinti navigáció hiánya és a tetszőleges oldalra ugrás lehetetlensége.
  • Megvalósítás WHERE id-t használ a kurzor után, after/before paramétereket és pageInfo-t a válaszban.
  • Szabvány — Relay Connection GraphQL, de a kurzorparaméteres REST API is széles körben elterjedt.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is