Cursor Pagination dalam pengembangan seluler — apa itu, prinsip dan implementasi

Penulis: IT Sectr Diterbitkan: 2026-03-11 Waktu membaca: 9 mnt

Cursor Pagination — metode halaman data yang menggunakan kursor unik untuk navigasi melalui kumpulan catatan yang terurut. Menurut GraphQL Specification (2025), halaman kursor adalah standar yang direkomendasikan untuk API yang bekerja dengan data dinamis. Cursor Pagination menghilangkan kelemahan utama pendekatan Offset: ketidakstabilan saat penyisipan dan penurunan kinerja pada pergeseran besar.

Poin utama

  • Cursor Pagination — metode halaman di mana setiap catatan memiliki pengenal-kursor unik untuk navigasi.
  • Kursor — penanda posisi unik dalam kumpulan data (biasanya ID, UUID, timestamp), yang tidak berubah saat penyisipan.
  • Stabilitas — catatan baru yang ditambahkan di antara permintaan tidak menggeser kursor, menghilangkan duplikat dan celah.
  • Kinerja — kueri WHERE id > cursor menggunakan indeks secara efisien, tanpa kehilangan kecepatan pada kumpulan besar.
  • Keterbatasan — halaman kursor tidak mendukung navigasi berdasarkan nomor halaman (tidak bisa melompat ke halaman 5).

Apa itu Cursor Pagination?

Cursor Pagination (halaman kursor) — metode halaman di mana server mengembalikan bersama dengan data sebuah penunjuk khusus — kursor. Klien menggunakan kursor ini dalam permintaan berikutnya untuk mendapatkan bagian catatan berikutnya. Kursor adalah pengenal unik dari elemen terakhir halaman saat ini.

Berbeda dengan Offset-pagination, di mana klien mengatakan “beri saya halaman 5 dengan 20 catatan”, halaman kursor bekerja berbeda: “beri saya 20 catatan setelah catatan dengan ID = 83”. Server menjalankan kueri dengan kondisi WHERE id > 83 dan LIMIT 20. Pendekatan ini menjamin bahwa setiap catatan masuk tepat ke satu halaman terlepas dari penyisipan.

Konsep halaman kursor telah menyebar luas berkat spesifikasi Relay Connection (GraphQL), yang menjadikan cursor-based pagination standar untuk API modern. Relay mendefinisikan format respons: edges (array catatan dengan kursor), pageInfo (hasNextPage, hasPreviousPage, startCursor, endCursor).

Sejarah kemunculan

Cursor-pagination bukanlah teknik baru — telah digunakan dalam basis data jauh sebelum munculnya web. Dalam SQL disebut keyset pagination atau seek method. Metode ini menjadi populer di API setelah publikasi spesifikasi Relay pada tahun 2015, yang meresmikan format kursor sebagai string berenkode base64 untuk keseragaman transmisi melalui HTTP.

Bagaimana cara kerja halaman kursor

Prinsip dasar halaman kursor — kueri menggunakan kondisi WHERE pada bidang terindeks untuk memposisikan, bukan pergeseran. Untuk arah maju digunakan WHERE id > last_id, untuk arah mundur — WHERE id < first_id. Indeks B-tree memungkinkan menemukan catatan pertama setelah kursor dalam O(log n), yang memberikan waktu respons yang stabil.

sql
-- Ambil 20 catatan setelah kursor '83'
SELECT id, title, created_at
FROM posts
WHERE id < 83
ORDER BY id DESC
LIMIT 20;

-- Ambil 20 catatan SEBELUM kursor '83' (mundur)
SELECT id, title, created_at
FROM posts
WHERE id > 83
ORDER BY id ASC
LIMIT 20;

Format kursor

Kursor bisa sederhana (nilai ID) atau kompleks (terdiri dari beberapa bidang). Kursor sederhana adalah kunci utama catatan, misalnya id auto-increment atau UUID. Kursor kompleks digunakan untuk pengurutan berdasarkan bidang tidak unik, misalnya (created_at, id), di mana id menjamin keunikan pada stempel waktu yang identik.

Format API tipikal — kursor dalam bentuk string berenkode base64. Server mendekode kursor, mengekstrak nilai dan membangun kueri SQL. Encoding Base64 menyembunyikan struktur internal kursor dari klien dan memungkinkan mengubah format tanpa kehilangan kompatibilitas mundur. Klien menerima kursor di bidang endCursor respons dan mengirimkannya sebagai string dalam permintaan berikutnya.

Navigasi maju dan mundur

Halaman kursor mendukung navigasi dua arah. Untuk gerakan maju (next) digunakan kursor elemen terakhir halaman saat ini, untuk mundur (previous) — kursor elemen pertama. Parameter after dan before dalam permintaan menentukan arah: after mengambil catatan setelah kursor, before — sebelum kursor.

Cursor vs Offset pagination

Pilihan antara halaman kursor dan Offset-pagination — salah satu pertanyaan arsitektural kunci saat merancang API. Setiap metode memiliki kelebihan dan kelemahan. Cursor-pagination unggul dalam skenario dengan data dinamis, Offset — dalam skenario dengan navigasi arbitrer.

KarakteristikCursorOffset
Stabilitas saat penyisipanTinggi (tanpa duplikat)Rendah (pergeseran halaman)
Kinerja pada kumpulan besarO(log n) — stabilO(n) — menurun seiring pertumbuhan
Navigasi berdasarkan nomor halamanTidakYa (page=5)
Kompleksitas implementasiSedangRendah
Dukungan di RESTcursor/before/afterpage/offset
Dukungan di GraphQLStandar RelayTidak direkomendasikan

Mengapa Offset kalah dalam skala besar

Offset-pagination melakukan pemindaian tabel penuh hingga posisi OFFSET. Pada offset=100000, basis data membaca dan melewatkan 100000 baris, bahkan jika LIMIT adalah 20. MySQL dan PostgreSQL tidak dapat mengoptimalkan OFFSET — ini adalah fitur implementasi LIMIT/OFFSET di SQL. Cursor-pagination menggunakan indeks B-tree, yang menemukan posisi dalam O(log n).

Masalah tambahan Offset — “melewatkan” catatan saat halaman mundur. Jika pengguna telah memuat halaman 5, dan pada saat itu catatan baru ditambahkan, saat meminta halaman 6 ia akan melihat catatan dari halaman 5 lagi atau melewatkan yang baru. Cursor-pagination sepenuhnya menghilangkan skenario ini: kursor menunjuk ke tempat tertentu dalam kumpulan, dan penyisipan tidak mengubah posisi.

Implementasi Cursor Pagination

Mari kita lihat implementasi halaman kursor di backend (Kotlin + Spring) dan di klien (Android + Retrofit). Server menerima parameter after, before, limit dan mengembalikan daftar catatan dengan kursor dan pageInfo. Respons tipikal berisi hasNextPage dan hasPreviousPage untuk mengelola UI halaman.

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

Implementasi klien di Android

Di sisi klien, halaman kursor diimplementasikan melalui PagingSource dari Paging 3, di mana kuncinya adalah kursor (Long). PagingSource.load menerima LoadParams.key — kursor catatan terakhir yang dimuat. LoadResult.Page mengembalikan data dan nextKey — kursor untuk halaman berikutnya. Ketika nextKey = null — halaman selesai.

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

// PagingSource dengan kunci kursor
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)
    }
}

Implementasi GraphQL melalui Relay

Dalam GraphQL, halaman kursor diimplementasikan melalui pola Connection Relay. Setiap tipe memiliki Connection (dengan pageInfo dan edges) dan Edge (node + cursor). Permintaan mengirimkan parameter first, after, last, before. Server mengembalikan array edges dengan kursor dan pageInfo dengan hasNextPage/hasPreviousPage.

Kapan menggunakan Cursor Pagination

Cursor-pagination direkomendasikan untuk API yang bekerja dengan data dinamis, di mana catatan sering ditambahkan atau dihapus. Contoh klasik: feed berita di jejaring sosial, pesan chat, riwayat transaksi, komentar pada postingan. Dalam semua skenario ini, konsistensi dan tidak adanya duplikat adalah penting.

  • Chat dan messenger — setiap pesan baru ditambahkan ke atas daftar. Offset-pagination terganggu dengan setiap pesan baru.
  • Jejaring sosial dan feed — postingan dipublikasikan secara terus-menerus. Halaman kursor menjamin pengguna tidak melewatkan satu postingan pun.
  • Riwayat pesanan dan transaksi — data lebih jarang berubah, tetapi konsistensi sangat penting untuk pelaporan keuangan.
  • API dengan volume data besar — jutaan catatan. Cursor-pagination mempertahankan kinerja di mana Offset mulai melambat.
  • GraphQL API — standar Relay mewajibkan penggunaan cursor-based pagination sesuai spesifikasi.
  • Aplikasi seluler dengan scroll tak terbatas — pengguna scroll ke bawah, memuat bagian baru. Pendekatan kursor memberikan UX yang mulus tanpa duplikat.

Kapan Cursor-pagination tidak cocok

Ada skenario di mana Offset-pagination lebih nyaman: panel administrasi di mana navigasi berdasarkan nomor halaman diperlukan; pencarian dengan halaman di mana hasil dapat berubah; laporan dan analitik di mana tautan tetap ke halaman 5 diperlukan. Dalam kasus ini, kelebihan kursor tidak melebihi kompleksitas implementasi.

Halaman kursor tidak mendukung “lompatan” ke halaman arbitrer — pengguna tidak bisa mengklik “Halaman 5” dan pergi ke sana. Ini adalah keterbatasan arsitektural: untuk menghitung jumlah total halaman diperlukan kueri COUNT terpisah, yang bisa mahal untuk tabel besar. Dalam kasus seperti itu, pendekatan hibrida: cursor untuk data + count untuk halaman.

Pertanyaan yang sering diajukan

Apa itu kursor dalam Cursor Pagination?

Kursor adalah pengenal unik catatan yang menunjukkan posisi dalam kumpulan data. Bisa sederhana (ID catatan) atau kompleks (beberapa bidang). Klien menerima kursor catatan terakhir halaman dan mengirimkannya dalam permintaan berikutnya untuk mendapatkan bagian berikutnya.

Apa kelebihan Cursor Pagination dibanding Offset?

Cursor-pagination tidak mengalami pergeseran saat penambahan catatan baru — setiap elemen masuk tepat ke satu halaman. Juga mempertahankan kecepatan pada volume besar berkat penggunaan indeks sebagai ganti pemindaian n baris pertama. Offset lebih sederhana tetapi tidak stabil untuk data dinamis.

Bisakah halaman kursor diimplementasikan tanpa GraphQL?

Ya, halaman kursor tidak terikat pada GraphQL. Dapat diimplementasikan di REST API mana pun dengan mengirimkan kursor sebagai parameter kueri ?after=83&limit=20. Respons harus berisi pageInfo dengan endCursor dan hasNextPage — ini memungkinkan klien mengelola pemuatan tanpa mengetahui struktur internal kursor.

Kursor mana yang digunakan — ID, UUID atau timestamp?

ID auto-increment — pilihan optimal: meningkat secara monoton, tidak berubah, diindeks secara efisien. UUID v7 (terurut waktu) juga cocok. Timestamp dapat memberikan duplikat pada waktu yang sama, oleh karena itu dikombinasikan dengan ID: (created_at, id) untuk menjamin keunikan kursor.

Bagaimana cara mengetahui jumlah total halaman pada halaman kursor?

Halaman kursor tidak memberikan jumlah total halaman — ini adalah keterbatasannya. Jika informasi tentang total diperlukan, jalankan kueri COUNT terpisah dengan filter yang sama. Untuk tabel besar, gunakan perhitungan perkiraan melalui EXPLAIN atau total yang di-cache dari analitik.

Ringkasan

  • Cursor Pagination — metode halaman dengan navigasi berdasarkan pengenal unik catatan, bukan pergeseran.
  • Kursor menjamin stabilitas kumpulan saat penyisipan: catatan baru tidak menggeser halaman yang sudah dimuat.
  • Kinerja pada volume data besar tetap tinggi (O(log n)) berkat penggunaan indeks B-tree.
  • Cursor-pagination cocok untuk data dinamis: chat, feed berita, transaksi, komentar.
  • Keterbatasan utama — tidak ada navigasi berdasarkan nomor halaman dan ketidakmampuan melompat ke halaman arbitrer.
  • Implementasi menggunakan WHERE id setelah kursor, parameter after/before dan pageInfo dalam respons.
  • Standar — Relay Connection GraphQL, tetapi REST API dengan parameter kursor juga tersebar luas.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga