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.
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:
- Kumpulan data kecil (< 10.000 catatan)
- API internal dengan penggunaan terkontrol
- Antarmuka admin di mana pengguna tidak akan melakukan paginasi mendalam
- Data yang jarang berubah
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
- Data diurutkan secara alami (berdasarkan timestamp, ID, dll.)
- Klien perlu memahami kunci paginasi
- Anda menginginkan paginasi transparan (bukan kursor buram)
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:
limit- Jumlah hasil per halaman (default: 20, maks: 100)cursor- Token paginasi buram dari respons sebelumnya
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?
- Ekstensibilitas - Dapat menambahkan metadata tanpa merusak klien
- Konsistensi - Semua titik akhir yang dipaginasi menggunakan format yang sama
- 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:
- Kumpulan data kecil (< 10.000 catatan)
- Pengguna membutuhkan akses acak (melompat ke halaman 50)
- Data jarang berubah
- API internal dengan penggunaan terkontrol
Jangan gunakan saat:
- Kumpulan data besar (> 100.000 catatan)
- Kinerja penting
- Data sering berubah
Paginasi Berbasis Kursor
Gunakan saat:
- Kumpulan data besar
- Kinerja penting
- Data sering berubah
- Akses berurutan sudah cukup
Jangan gunakan saat:
- Pengguna membutuhkan akses acak
- Kompleksitas kursor menjadi perhatian
Paginasi Keyset
Gunakan saat:
- Data diurutkan secara alami
- Paginasi transparan lebih disukai
- Kinerja penting
Jangan gunakan saat:
- Urutan pengurutan kompleks
- Beberapa bidang pengurutan diperlukan
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:
- Hindari paginasi offset untuk kumpulan data besar
- Gunakan paginasi berbasis kursor untuk skalabilitas
- Bungkus koleksi dengan metadata dan tautan
- Uji paginasi secara menyeluruh dengan Apidog
- Ikuti pola paginasi Modern PetstoreAPI
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.
