Bagaimana Mendesain Pagination API untuk Jutaan Data?

Ashley Innocent

Ashley Innocent

13 March 2026

Bagaimana Mendesain Pagination API untuk Jutaan Data?

Apidog untuk Perusahaan

Penerapan On-Premises

SSO & RBAC

Sesuai SOC 2

Jelajahi Apidog Enterprise

TL;DR

Untuk kumpulan data besar, gunakan paginasi berbasis kursor atau keyset alih-alih paginasi berbasis offset. Paginasi offset (?page=1&limit=20) berkinerja buruk dengan jutaan catatan dan memungkinkan inkonsistensi data. Modern PetstoreAPI mengimplementasikan paginasi berbasis kursor dengan token buram dan tautan HATEOAS untuk hasil yang efisien dan konsisten.

Pendahuluan

API Anda mengembalikan daftar hewan peliharaan. Anda memiliki 10 juta hewan peliharaan di database. Seorang klien meminta GET /pets?page=500000&limit=20. Database Anda mengeksekusi OFFSET 10000000 LIMIT 20. Kueri memakan waktu 30 detik. API Anda kehabisan waktu (timeout).

Ini adalah masalah paginasi offset. Ini berfungsi baik untuk kumpulan data kecil tetapi rusak pada skala besar. Database harus memindai jutaan baris untuk mencapai offset, meskipun Anda hanya mengembalikan 20 hasil.

Swagger Petstore yang lama sama sekali tidak membahas paginasi. Modern PetstoreAPI mengimplementasikan paginasi berbasis kursor yang dapat diskalakan hingga jutaan catatan dengan kinerja yang konsisten.

💡
Jika Anda membangun atau menguji REST API, Apidog membantu Anda menguji perilaku paginasi, memvalidasi format respons, dan memastikan API Anda menangani kumpulan data besar dengan benar. Anda dapat mensimulasikan skenario paginasi, menguji kasus ekstrem, dan memverifikasi kinerja.
tombol

Dalam panduan ini, Anda akan mempelajari mengapa paginasi offset gagal, bagaimana paginasi berbasis kursor bekerja, dan bagaimana Modern PetstoreAPI mengimplementasikan paginasi yang efisien.

Mengapa Paginasi Offset Gagal pada Skala Besar

Paginasi offset adalah pendekatan paling umum, tetapi memiliki masalah serius.

Bagaimana Paginasi Offset Bekerja

GET /pets?page=1&limit=20    → OFFSET 0 LIMIT 20
GET /pets?page=2&limit=20    → OFFSET 20 LIMIT 20
GET /pets?page=3&limit=20    → OFFSET 40 LIMIT 20

Database melewati offset baris dan mengembalikan limit baris.

Masalah 1: Kinerja Menurun dengan Nomor Halaman

Halaman 1:

SELECT * FROM pets OFFSET 0 LIMIT 20;
-- Cepat: memindai 20 baris

Halaman 1000:

SELECT * FROM pets OFFSET 20000 LIMIT 20;
-- Lambat: memindai 20.020 baris, mengembalikan 20

Halaman 500.000:

SELECT * FROM pets OFFSET 10000000 LIMIT 20;
-- Sangat lambat: memindai 10.000.020 baris, mengembalikan 20

Database harus memindai semua baris hingga offset, meskipun membuangnya. Kinerja menurun secara linier dengan nomor halaman.

Masalah 2: Hasil Tidak Konsisten

Saat klien menelusuri hasil, data berubah:

Permintaan 1:

GET /pets?page=1&limit=2
Mengembalikan: [Pet A, Pet B]

Seseorang menambahkan Hewan Peliharaan Z (diurutkan pertama secara alfabetis)

Permintaan 2:

GET /pets?page=2&limit=2
Mengembalikan: [Pet B, Pet C]  ← Hewan Peliharaan B muncul dua kali!

Hewan Peliharaan B muncul di kedua halaman karena hewan peliharaan baru dimasukkan. Sebaliknya, hewan peliharaan dapat dilewati jika terjadi penghapusan.

Masalah 3: Paginasi Mendalam Mahal

Pengguna jarang melampaui halaman 10. Tetapi jika API Anda mengizinkan ?page=1000000, Anda harus menanganinya. Kueri paginasi mendalam mahal dan dapat digunakan untuk serangan denial-of-service.

Kapan Paginasi Offset Dapat Diterima

Paginasi offset berfungsi baik untuk:

Untuk API publik atau kumpulan data besar, gunakan paginasi berbasis kursor.

Paginasi Berbasis Kursor Dijelaskan

Paginasi berbasis kursor menggunakan token buram untuk menandai posisi dalam kumpulan hasil.

Cara Kerjanya

Permintaan 1:

GET /pets?limit=20

Respons 1:

{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9",
    "hasMore": true
  }
}

Permintaan 2:

GET /pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20

Kursor adalah token buram (biasanya dienkode base64) yang mengodekan posisi. Klien tidak menguraikannya—hanya meneruskannya kembali.

Manfaat

1. Kinerja Konsisten

Database menggunakan indeks untuk menemukan posisi kursor secara langsung:

SELECT * FROM pets
WHERE id > '019b4132-70aa-764f-b315-e2803d882a24'
ORDER BY id
LIMIT 20;

Kueri ini cepat terlepas dari posisi dalam kumpulan data. Ini menggunakan pencarian indeks, bukan pemindaian.

2. Hasil Konsisten

Kursor stabil. Jika data berubah di antara permintaan, Anda tetap mendapatkan hasil yang konsisten. Catatan baru tidak menyebabkan duplikat atau terlewat.

3. Tidak Ada Serangan Paginasi Mendalam

Klien tidak bisa melompat ke posisi sembarang. Mereka harus melakukan paginasi secara berurutan, yang membatasi penyalahgunaan.

Format Kursor

Kursor biasanya JSON yang dienkode base64:

// Kursor yang Didekode
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "createdAt": "2026-03-13T10:30:00Z"
}

Kursor berisi informasi yang cukup untuk melanjutkan paginasi. Untuk Modern PetstoreAPI, ini termasuk ID sumber daya dan bidang pengurutan.

Paginasi Keyset untuk Data Terurut

Paginasi keyset adalah varian dari paginasi berbasis kursor untuk data yang terurut.

Cara Kerjanya

Alih-alih kursor buram, Anda menggunakan nilai terakhir dari halaman sebelumnya:

Permintaan 1:

GET /pets?limit=20&sortBy=createdAt

Respons 1:

{
  "data": [
    {"id": "...", "createdAt": "2026-03-13T10:00:00Z"},
    ...
    {"id": "...", "createdAt": "2026-03-13T10:30:00Z"}
  ]
}

Permintaan 2:

GET /pets?limit=20&sortBy=createdAt&after=2026-03-13T10:30:00Z

Parameter after menggunakan nilai createdAt terakhir dari halaman sebelumnya.

Kueri SQL

SELECT * FROM pets
WHERE created_at > '2026-03-13T10:30:00Z'
ORDER BY created_at
LIMIT 20;

Ini efisien karena menggunakan indeks pada created_at.

Kapan Menggunakan Paginasi Keyset

Modern PetstoreAPI menggunakan paginasi berbasis kursor secara default tetapi mendukung paginasi keyset untuk data deret waktu.

Bagaimana Modern PetstoreAPI Mengimplementasikan Paginasi

Modern PetstoreAPI menggunakan paginasi berbasis kursor dengan tautan HATEOAS.

Format Permintaan

GET /pets?limit=20
GET /pets?cursor={token}&limit=20

Parameter:

Format Respons

{
  "data": [
    {
      "id": "019b4132-70aa-764f-b315-e2803d882a24",
      "name": "Fluffy",
      "species": "CAT"
    }
  ],
  "pagination": {
    "limit": 20,
    "hasMore": true,
    "nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9"
  },
  "links": {
    "self": "https://petstoreapi.com/pets?limit=20",
    "next": "https://petstoreapi.com/pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20"
  }
}

Fitur Utama

1. Kursor Buram

Kursor dienkode base64. Klien tidak menguraikannya.

2. Tautan HATEOAS

Objek links menyediakan URL yang siap digunakan. Klien tidak perlu membuat URL paginasi.

3. Bendera hasMore

Menunjukkan apakah ada lebih banyak hasil. Klien tahu kapan harus berhenti melakukan paginasi.

4. Validasi Batas

Batas maksimum adalah 100. Mencegah klien meminta halaman yang sangat besar.

Lihat dokumentasi paginasi Modern PetstoreAPI untuk detail lengkapnya.

Format Respons Paginasi

Modern PetstoreAPI membungkus respons paginasi dalam struktur yang konsisten.

Pembungkus Koleksi

{
  "data": [...],
  "pagination": {...},
  "links": {...}
}

Mengapa membungkus koleksi?

  1. Ekstensibilitas - Dapat menambahkan metadata tanpa merusak klien
  2. Konsistensi - Semua titik akhir yang dipaginasi menggunakan format yang sama
  3. HATEOAS - Tautan memandu klien melalui paginasi

Metadata Paginasi

"pagination": {
  "limit": 20,
  "hasMore": true,
  "nextCursor": "...",
  "totalCount": 1000  // Opsional, mahal untuk dihitung
}

totalCount bersifat opsional karena menghitungnya mahal untuk kumpulan data besar. Sebagian besar klien tidak membutuhkannya.

Menguji Paginasi dengan Apidog

Apidog membantu Anda menguji perilaku paginasi secara komprehensif.

Skenario Uji

1. Halaman Pertama

GET /pets?limit=20
Harapkan: 20 hasil, hasMore=true, nextCursor ada

2. Halaman Berikutnya

GET /pets?cursor={token}&limit=20
Harapkan: 20 hasil, hasMore=true/false, nextCursor ada/tidak ada

3. Halaman Terakhir

GET /pets?cursor={lastToken}&limit=20
Harapkan: < 20 hasil, hasMore=false, tidak ada nextCursor

4. Hasil Kosong

GET /pets?status=NONEXISTENT&limit=20
Harapkan: 0 hasil, hasMore=false, tidak ada nextCursor

5. Validasi Batas

GET /pets?limit=1000
Harapkan: 400 Bad Request (melebihi batas maksimum)

Konfigurasi Uji Apidog

// Uji: Struktur paginasi
pm.test("Response has pagination", () => {
  pm.expect(pm.response.json()).to.have.property('pagination');
  pm.expect(pm.response.json().pagination).to.have.property('hasMore');
});

// Uji: Tautan HATEOAS
pm.test("Response has links", () => {
  const links = pm.response.json().links;
  pm.expect(links).to.have.property('self');
  if (pm.response.json().pagination.hasMore) {
    pm.expect(links).to.have.property('next');
  }
});

Memilih Strategi Paginasi yang Tepat

Strategi yang berbeda cocok untuk kasus penggunaan yang berbeda.

Paginasi Offset

Gunakan saat:

Jangan gunakan saat:

Paginasi Berbasis Kursor

Gunakan saat:

Jangan gunakan saat:

Paginasi Keyset

Gunakan saat:

Jangan gunakan saat:

Rekomendasi Modern PetstoreAPI: Gunakan paginasi berbasis kursor untuk API publik dan kumpulan data besar.

Kesimpulan

Paginasi sangat penting untuk API yang mengembalikan kumpulan data besar. Paginasi offset sederhana tetapi tidak skalabel. Paginasi berbasis kursor memberikan kinerja yang konsisten dan hasil yang andal untuk jutaan catatan.

Modern PetstoreAPI mengimplementasikan paginasi berbasis kursor dengan token buram, tautan HATEOAS, dan metadata yang tepat. Desain ini skalabel secara efisien dan memberikan pengalaman pengembang yang luar biasa.

Uji implementasi paginasi Anda dengan Apidog untuk memastikan ia menangani kasus ekstrem, memvalidasi batasan, dan mengembalikan hasil yang konsisten.

Poin-poin penting:

tombol

FAQ

Mengapa tidak mengembalikan semua hasil tanpa paginasi?

Mengembalikan jutaan catatan dalam satu respons menyebabkan masalah memori, transfer jaringan yang lambat, dan pengalaman pengguna yang buruk. Paginasi sangat penting untuk kumpulan data besar.

Bisakah klien melompat ke halaman tertentu dengan paginasi kursor?

Tidak, paginasi kursor memerlukan akses berurutan. Jika akses acak diperlukan, pertimbangkan paginasi offset untuk kumpulan data kecil atau implementasikan pencarian/pemfilteran sebagai gantinya.

Bagaimana cara menangani paginasi dengan pemfilteran?

Sertakan parameter filter dalam permintaan paginasi: GET /pets?status=AVAILABLE&cursor={token}&limit=20. Kursor mengodekan posisi dan status filter.

Haruskah saya menyertakan jumlah total dalam respons paginasi?

Hanya jika klien membutuhkannya dan kumpulan data Anda kecil. Menghitung jumlah total mahal untuk kumpulan data besar (memerlukan kueri COUNT terpisah).

Bagaimana cara mengimplementasikan paginasi kursor di SQL?

Gunakan klausa WHERE dengan nilai kursor: SELECT * FROM pets WHERE id > ? ORDER BY id LIMIT 20. Pastikan Anda memiliki indeks pada kolom pengurutan.

Bagaimana jika token kursor saya menjadi tidak valid?

Kembalikan 400 Bad Request dengan pesan kesalahan. Kursor dapat menjadi tidak valid jika data dihapus atau status paginasi kedaluwarsa.

Berapa lama kursor harus tetap valid?

Kursor Modern PetstoreAPI berlaku tanpa batas waktu selama sumber daya yang direferensikan ada. Beberapa API mengkadaluwarsakan kursor setelah 24 jam.

Bisakah saya menggunakan paginasi kursor dengan beberapa bidang pengurutan?

Ya, tetapi kursor harus mengodekan semua bidang pengurutan. Ini membuat kursor lebih kompleks. Pertimbangkan untuk menggunakan satu kunci pengurutan komposit sebagai gantinya.

Mengembangkan API dengan Apidog

Apidog adalah alat pengembangan API yang membantu Anda mengembangkan API dengan lebih mudah dan efisien.

Bagaimana Mendesain Pagination API untuk Jutaan Data?