Cursor Pagination sa mobile development — ano ito, prinsipyo at implementasyon

May-akda: IT Sectr Nai-publish: 2026-03-11 Oras ng pagbabasa: 9 min

Cursor Pagination — paraan ng pag-pagina ng data na gumagamit ng natatanging cursor para mag-navigate sa isang nakaayos na set ng mga record. Ayon sa GraphQL Specification (2025), ang cursor pagination ay ang inirerekomendang pamantayan para sa mga API na nagtatrabaho sa dinamikong data. Cursor Pagination inaalis ang mga pangunahing disadvantage ng Offset approach: kawalan ng katatagan sa pagpasok at pagbaba ng performance sa malalaking paglilipat.

Mga pangunahing punto

  • Cursor Pagination — paraan ng pagina kung saan ang bawat record ay may natatanging identifier-cursor para sa nabigasyon.
  • Cursor — natatanging marker ng posisyon sa set ng data (karaniwang ID, UUID, timestamp), na hindi nagbabago sa pagpasok.
  • Katatagan — ang mga bagong record na idinagdag sa pagitan ng mga kahilingan ay hindi naglilipat ng cursor, inaalis ang mga duplicate at puwang.
  • Performance — ang query na WHERE id > cursor ay epektibong gumagamit ng index, hindi nawawala ang bilis sa malalaking set.
  • Limitasyon — ang cursor pagina ay hindi sumusuporta sa nabigasyon ayon sa numero ng pahina (hindi maaaring lumaktaw sa pahina 5).

Ano ang Cursor Pagination?

Cursor Pagination (cursor pagina) — paraan ng pagina kung saan ang server ay nagbabalik kasama ng data ng isang espesyal na pointer — cursor. Ginagamit ng client ang cursor na ito sa susunod na kahilingan para makuha ang susunod na bahagi ng mga record. Ang cursor ay ang natatanging identifier ng huling elemento ng kasalukuyang pahina.

Hindi tulad ng Offset-pagination, kung saan sinasabi ng client “bigyan mo ako ng pahina 5 na may 20 record”, ang cursor pagina ay gumagana nang iba: “bigyan mo ako ng 20 record pagkatapos ng record na may ID = 83”. Isinasagawa ng server ang query na may kondisyong WHERE id > 83 at LIMIT 20. Ang pamamaraang ito ay ginagarantiyahan na ang bawat record ay mapupunta nang eksakto sa isang pahina anuman ang mga pagpasok.

Ang konsepto ng cursor pagina ay naging laganap dahil sa specifikasyon ng Relay Connection (GraphQL), na ginawang pamantayan ang cursor-based pagination para sa mga modernong API. Tinutukoy ng Relay ang format ng tugon: edges (array ng mga record na may cursor), pageInfo (hasNextPage, hasPreviousPage, startCursor, endCursor).

Kasaysayan ng paglitaw

Ang cursor-pagination ay hindi isang bagong teknik — ginamit ito sa mga database bago pa lumitaw ang web. Sa SQL ito ay tinatawag na keyset pagination o seek method. Naging popular ang paraan sa mga API pagkatapos ng paglalathala ng specifikasyon ng Relay noong 2015, na nagpormalisa sa format ng cursor bilang base64-encoded string para sa pagkakapareho ng pagpapadala sa pamamagitan ng HTTP.

Paano gumagana ang cursor pagina

Ang pangunahing prinsipyo ng cursor pagina — ang query ay gumagamit ng WHERE condition sa isang naka-index na field para sa pagpoposisyon, hindi paglilipat. Para sa pasulong na direksyon ginagamit ang WHERE id > last_id, para sa pabalik na direksyon — WHERE id < first_id. Ang B-tree index ay nagbibigay-daan na mahanap ang unang record pagkatapos ng cursor sa O(log n), na nagbibigay ng stable na oras ng tugon.

sql
-- Kumuha ng 20 record pagkatapos ng cursor '83'
SELECT id, title, created_at
FROM posts
WHERE id < 83
ORDER BY id DESC
LIMIT 20;

-- Kumuha ng 20 record BAGO ang cursor '83' (pabalik)
SELECT id, title, created_at
FROM posts
WHERE id > 83
ORDER BY id ASC
LIMIT 20;

Format ng cursor

Ang cursor ay maaaring simple (halaga ng ID) o komplikado (binubuo ng maraming field). Ang mga simpleng cursor ay ang pangunahing key ng record, halimbawa auto-increment id o UUID. Ang mga komplikadong cursor ay ginagamit para sa pag-uuri ayon sa hindi natatanging mga field, halimbawa (created_at, id), kung saan ginagarantiyahan ng id ang pagiging natatangi sa magkaparehong timestamp.

Typical na format ng API — cursor sa anyo ng base64-encoded string. Ide-decode ng server ang cursor, kukunin ang halaga at bubuo ng SQL query. Ang Base64 encoding ay nagtatago ng panloob na istraktura ng cursor mula sa client at nagpapahintulot na baguhin ang format nang hindi nawawala ang backward compatibility. Natatanggap ng client ang mga cursor sa endCursor field ng tugon at ipinapasa ang mga ito bilang string sa susunod na kahilingan.

Nabigasyon pasulong at pabalik

Ang cursor pagina ay sumusuporta sa bidirectional nabigasyon. Para sa pasulong na galaw (next) ginagamit ang cursor ng huling elemento ng kasalukuyang pahina, para sa pabalik (previous) — cursor ng unang elemento. Ang mga parameter na after at before sa kahilingan ay tumutukoy sa direksyon: after kumukuha ng mga record pagkatapos ng cursor, before — bago ang cursor.

Cursor vs Offset pagination

Ang pagpili sa pagitan ng cursor at Offset-pagination — isa sa mga pangunahing katanungang arkitektural sa pagdidisenyo ng API. Ang bawat pamamaraan ay may malakas at mahinang panig. Cursor-pagination nananalo sa mga senaryo na may dinamikong data, Offset — sa mga senaryo na may arbitraryong nabigasyon.

KatangianCursorOffset
Katatagan sa pagpasokMataas (walang duplicate)Mababa (paglilipat ng pahina)
Performance sa malalaking setO(log n) — stableO(n) — bumababa sa paglaki
Nabigasyon ayon sa numero ng pahinaHindiOo (page=5)
Kompleksidad ng implementasyonKatamtamanMababa
Suporta sa RESTcursor/before/afterpage/offset
Suporta sa GraphQLPamantayan ng RelayHindi inirerekomenda

Bakit natatalo ang Offset sa malaking scale

Ang Offset-pagination ay nagsasagawa ng buong pag-scan ng talahanayan hanggang sa posisyong OFFSET. Sa offset=100000, binabasa at nilalaktawan ng database ang 100000 na row, kahit na ang LIMIT ay 20. Ang MySQL at PostgreSQL ay hindi maaaring i-optimize ang OFFSET — ito ay katangian ng implementasyon ng LIMIT/OFFSET sa SQL. Ang Cursor-pagination ay gumagamit ng B-tree index, na nakakahanap ng posisyon sa O(log n).

Ang karagdagang problema ng Offset — “paglaktaw” ng mga record sa pabalik na pagina. Kung ang user ay nag-load ng pahina 5, at sa sandaling iyon ay may mga bagong record na idinagdag, sa paghingi ng pahina 6 makikita niya ang record mula sa pahina 5 muli o lalaktawan ang mga bago. Ang Cursor-pagination ay ganap na inaalis ang senaryong ito: ang cursor ay tumuturo sa isang tiyak na lugar sa set, at ang mga pagpasok ay hindi nagbabago ng posisyon.

Implementasyon ng Cursor Pagination

Tingnan natin ang implementasyon ng cursor pagina sa backend (Kotlin + Spring) at sa client (Android + Retrofit). Ang server ay tumatanggap ng mga parameter na after, before, limit at nagbabalik ng listahan ng mga record na may cursor at pageInfo. Ang typical na tugon ay naglalaman ng hasNextPage at hasPreviousPage para sa pamamahala ng UI ng pagina.

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
        )
    )
}

Implementasyon ng client sa Android

Sa panig ng client, ang cursor pagina ay ipinapatupad sa pamamagitan ng PagingSource mula sa Paging 3, kung saan ang susi ay ang cursor (Long). Ang PagingSource.load ay tumatanggap ng LoadParams.key — cursor ng huling na-load na record. Ang LoadResult.Page ay nagbabalik ng data at nextKey — cursor para sa susunod na pahina. Kapag ang nextKey = null — ang pagina ay natapos na.

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

// PagingSource na may cursor key
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)
    }
}

Implementasyon ng GraphQL sa pamamagitan ng Relay

Sa GraphQL, ang cursor pagina ay ipinapatupad sa pamamagitan ng pattern ng Connection Relay. Ang bawat uri ay may Connection (na may pageInfo at edges) at Edge (node + cursor). Ang kahilingan ay nagpapadala ng mga parameter na first, after, last, before. Ang server ay nagbabalik ng array ng edges na may cursor at pageInfo na may hasNextPage/hasPreviousPage.

Kailan gagamitin ang Cursor Pagination

Ang cursor-pagination ay inirerekomenda para sa mga API na nagtatrabaho sa dinamikong data, kung saan ang mga record ay madalas na idinadagdag o binubura. Mga klasikong halimbawa: feed ng balita sa social network, mga mensahe sa chat, kasaysayan ng transaksyon, mga komento sa post. Sa lahat ng mga senaryong ito, mahalaga ang pagkakapare-pareho at kawalan ng mga duplicate.

  • Mga Chat at messenger — bawat bagong mensahe ay idinadagdag sa tuktok ng listahan. Ang Offset-pagination ay naaabala sa bawat bagong mensahe.
  • Mga social network at feed — ang mga post ay patuloy na nai-publish. Ang cursor pagina ay ginagarantiyahan na hindi makaligtaan ang user ng anumang post.
  • Kasaysayan ng order at transaksyon — ang data ay mas madalang magbago, ngunit ang pagkakapare-pareho ay kritikal para sa financial reporting.
  • Mga API na may malaking volume ng data — milyong mga record. Ang Cursor-pagination ay nagpapanatili ng performance kung saan ang Offset ay nagsisimulang bumagal.
  • GraphQL API — ang pamantayan ng Relay ay nag-aatas ng paggamit ng cursor-based pagination ayon sa specifikasyon.
  • Mga mobile app na may walang katapusang scroll — ang user ay nag-scroll pababa, naglo-load ng mga bagong bahagi. Ang diskarteng cursor ay nagbibigay ng maayos na UX nang walang duplicate.

Kailan hindi angkop ang Cursor-pagination

May mga senaryo kung saan ang Offset-pagination ay mas maginhawa: mga administrative panel kung saan kinakailangan ang nabigasyon ayon sa numero ng pahina; paghahanap na may pagina kung saan ang mga resulta ay maaaring magbago; mga ulat at analytics kung saan kailangan ang nakapirming link sa pahina 5. Sa mga kasong ito, ang mga bentahe ng cursor ay hindi hihigit sa kompleksidad ng implementasyon.

Ang cursor pagina ay hindi sumusuporta sa “ pagtalon” sa arbitraryong pahina — hindi maaaring mag-click ang user ng “Pahina 5” at pumunta doon. Ito ay isang limitasyong arkitektural: para sa pagkalkula ng kabuuang bilang ng mga pahina kinakailangan ang isang hiwalay na COUNT query, na maaaring mahal para sa malalaking talahanayan. Sa ganitong mga kaso, hybrid na diskarte: cursor para sa data + count para sa pagina.

Mga madalas itanong

Ano ang cursor sa Cursor Pagination?

Ang cursor ay ang natatanging identifier ng record na nagpapakita ng posisyon sa set ng data. Maaaring simple (ID ng record) o komplikado (maraming field). Ang client ay tumatanggap ng cursor ng huling record ng pahina at ipinapasa ito sa susunod na kahilingan para makuha ang susunod na bahagi.

Paano mas mahusay ang Cursor Pagination kaysa Offset?

Ang cursor-pagination ay hindi napapailalim sa paglilipat sa pagdaragdag ng mga bagong record — bawat elemento ay napupunta nang eksakto sa isang pahina. Pinapanatili rin nito ang bilis sa malalaking volume dahil sa paggamit ng mga index sa halip na pag-scan ng unang n row. Ang Offset ay mas simple ngunit hindi matatag para sa dinamikong data.

Maaari bang ipatupad ang cursor pagina nang walang GraphQL?

Oo, ang cursor pagina ay hindi nakatali sa GraphQL. Maaari itong ipatupad sa anumang REST API sa pamamagitan ng pagpapasa ng cursor bilang query parameter ?after=83&limit=20. Ang tugon ay dapat maglaman ng pageInfo na may endCursor at hasNextPage — ito ay nagbibigay-daan sa client na pamahalaan ang pag-load nang hindi alam ang panloob na istraktura ng cursor.

Aling cursor ang gagamitin — ID, UUID o timestamp?

Auto-increment ID — pinakamainam na pagpili: monotonikong tumataas, hindi nagbabago, mahusay na nai-index. Ang UUID v7 (nakaayos ayon sa oras) ay angkop din. Ang timestamp ay maaaring magbigay ng mga duplicate sa parehong oras, kaya pinagsama ito sa ID: (created_at, id) para sa garantiya ng pagiging natatangi ng cursor.

Paano malalaman ang kabuuang bilang ng mga pahina sa cursor pagina?

Ang cursor pagina ay hindi nagbibigay ng kabuuang bilang ng mga pahina — ito ang limitasyon nito. Kung kailangan ang impormasyon tungkol sa total, magsagawa ng hiwalay na COUNT query na may parehong mga filter. Para sa malalaking talahanayan, gumamit ng tinatayang pagbibilang sa pamamagitan ng EXPLAIN o naka-cache na total mula sa analytics.

Buod

  • Cursor Pagination — paraan ng pagina na may nabigasyon ayon sa natatanging identifier ng record sa halip na paglilipat.
  • Cursor ginagarantiyahan ang katatagan ng set sa pagpasok: ang mga bagong record ay hindi naglilipat ng mga nai-load nang pahina.
  • Performance sa malalaking volume ng data ay nananatiling mataas (O(log n)) dahil sa paggamit ng B-tree index.
  • Cursor-pagination ay angkop para sa dinamikong data: chat, news feed, transaksyon, komento.
  • Pangunahing limitasyon — kawalan ng nabigasyon ayon sa numero ng pahina at hindi maaaring lumaktaw sa arbitraryong pahina.
  • Implementasyon gumagamit ng WHERE id pagkatapos ng cursor, mga parameter na after/before at pageInfo sa tugon.
  • Pamantayan — Relay Connection GraphQL, ngunit ang REST API na may cursor parameter ay laganap din.

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din