Konteks dan arahan
Pertanyaan ini menguji apakah seorang backend engineer memperlakukan paginasi sebagai protokol baca yang stabil. Pesanan, komentar, dan log dapat dimasukkan, dihapus, atau diperbarui di antara permintaan. OFFSET standar bergantung pada posisi yang bergeser dan memindai banyak baris pada halaman yang dalam. Bahas pengurutan, pengkodean kursor, pengikatan filter, batasan konsistensi, indeks, serta semantik maju/mundur.
Hal yang diuji pewawancara
Jawaban yang kuat memperjelas apakah produk membutuhkan lompatan halaman, penghitungan jumlah, atau hasil waktu nyata, lalu memilih keyset/kursor atau offset. Kursor mengikat pengurutan dan filter; kunci pengurutannya bersifat unik, stabil, dan terindeks. Server menandatangani dan menetapkan masa berlakunya. Jawaban tersebut menjelaskan mengapa baris baru tidak menduplikasi halaman berikutnya, bagaimana penghapusan memengaruhi hasil, serta bagaimana next_cursor dan status akhir daftar dikembalikan.
Pertanyaan untuk klarifikasi
- Bagaimana urutan penyortirannya? Bisakah bidang sortir berubah, dan apa penentu keunikan (tie-breaker)-nya?
- Apakah kita memerlukan navigasi halaman sebelumnya, lompatan halaman arbitrer, hitungan persis, atau hanya infinite scroll ke depan?
- Haruskah pembacaan menggunakan snapshot beku atau mengizinkan daftar langsung yang konsisten pada akhirnya (eventually consistent)?
- Haruskah filter, cakupan tenant, izin, dan urutan penyortiran diikat ke dalam kursor? Berapa lama kursor valid?
- Bagaimana penanganan untuk penghapusan, soft delete, perubahan izin, dan pembacaan lintas-shard?
Kerangka jawaban 30 detik
"Saya akan menggunakan kunci komposit yang stabil seperti (created_at, id) untuk kueri keyset menurun. Kursor adalah token buram (opaque) bertanda tangan yang berisi nilai sortir terakhir, hash filter, arah, dan versi. Server memvalidasinya dan menjalankan WHERE (created_at,id) < (:time,:id) terhadap indeks komposit yang cocok. Baris baru akan muncul saat refresh alih-alih disisipkan ke dalam jendela yang sudah dibaca; penghapusan mungkin memperpendek halaman tetapi tidak membuat duplikat. Jika konsistensi mutlak diperlukan, saya akan menambahkan batasan snapshot dan menjelaskan biayanya."
Jawaban mendalam langkah demi langkah
Langkah 1: Tentukan semantik daftar
Tentukan apakah ini riwayat audit, feed langsung, atau tabel admin. Riwayat biasanya memerlukan batasan yang stabil; feed langsung mungkin menampilkan baris baru hanya setelah refresh. Jangan menjanjikan pembaruan waktu nyata, lompatan arbitrer, hitungan persis, dan biaya rendah secara bersamaan.
Langkah 2: Pilih kunci pengurutan yang stabil
Stempel waktu dapat bernilai sama atau diedit, jadi tambahkan id unik sebagai pemecah seri (tie-breaker). Hindari bidang tampilan. Jika pembaruan dapat memindahkan rekaman, gunakan urutan pembuatan yang tidak dapat diubah (immutable) atau dokumentasikan secara eksplisit bahwa baris dapat berpindah antar halaman.
Langkah 3: Rancang kursor buram (opaque cursor)
Sertakan nilai sortir, arah, hash filter, versi API, dan kedaluwarsa. Tandatangani atau simpan di sisi server; Base64 hanyalah pengkodean, bukan keamanan. Tolak kursor ketika filter berubah alih-alih mengembalikan halaman yang tidak terkait secara diam-diam.
Langkah 4: Tulis kueri keyset
Untuk (created_at,id) yang menurun, halaman berikutnya menggunakan created_at < t OR (created_at = t AND id < id0) dengan indeks komposit yang sama. Buat kueri berparameter, batasi limit, dan jangan pernah menggabungkan nilai kursor ke dalam SQL.
Langkah 5: Tangani penulisan dan penghapusan
Baris yang dimasukkan setelah halaman satu tidak boleh muncul di halaman dua; baris tersebut muncul setelah refresh. Baris yang dihapus dapat membuat halaman menjadi lebih pendek, yang merupakan semantik terdeklarasi yang dapat diterima. Jika penghilangan data tidak dapat diterima, gunakan batasan snapshot atau pembacaan berversi.
Langkah 6: Ikat izin dan filter
Hash filter, tenant, dan cakupan otorisasi pada kursor harus cocok dengan permintaan. Hitung ulang hasil saat izin diperketat; kursor lama tidak boleh melewati kontrol akses. Koordinator lintas-shard dapat menggabungkan kursor lokal, tetapi jawaban harus menyatakan biaya amplifikasinya.
Langkah 7: Tentukan respons dan kesalahan
Kembalikan items, next_cursor, has_more, dan secara opsional id snapshot. Kursor yang kedaluwarsa atau tidak valid serta filter yang diubah menggunakan kesalahan bisnis yang stabil; klien menghapus kursor dan memulai ulang dari halaman satu. Jangan mengekspos SQL atau kegagalan internal basis data.
Langkah 8: Validasi dengan pengujian konkurensi
Uji operasi penyisipan, penghapusan, stempel waktu yang sama, perubahan filter, kursor yang diutak-atik, dan halaman yang dalam di antara permintaan. Verifikasi tidak adanya duplikat untuk satu sesi, pencarian indeks (index seeks), dan latensi yang tidak tumbuh secara linier dengan nomor halaman. Lacak tingkat duplikasi, tingkat lewatan, latensi p95, dan kesalahan kursor.
Pseudocode kueri
SELECT id, created_at, total
FROM orders
WHERE tenant_id = :tenant
AND (created_at, id) < (:cursor_time, :cursor_id)
ORDER BY created_at DESC, id DESC
LIMIT :page_size;Pertukaran (trade-offs) dan batasan
| Persyaratan | Pilihan | Biaya |
|---|---|---|
| Infinite scroll besar | Kursor keyset | Tidak ada lompatan halaman arbitrer |
| Tabel admin kecil | Offset | Halaman dalam lambat dan tidak stabil di bawah penulisan |
| Konsistensi mutlak | Batasan snapshot | Penyimpanan dan pembersihan snapshot |
| Hitungan persis | Hitungan terpisah atau asinkron | Kerja ekstra dan potensi data basi (staleness) |
Paginasi kursor menyelesaikan stabilitas posisi dan efisiensi kueri; ini tidak secara otomatis menyelesaikan deduplikasi bisnis lintas halaman, perubahan izin, atau pembaruan yang memindahkan baris. Hasil pencarian juga memerlukan versi kueri; agregat mungkin memerlukan pembacaan point-in-time.
Rencana peluncuran dan bukti
Pilih daftar dengan tingkat pembacaan tinggi dan ukur duplikat, lewatan, latensi halaman dalam, dan biaya hitungan saat ini. Tambahkan covering index, rilis kursor berversi, dan tambahkan pengujian serta metrik penulisan konkuren. Django REST framework mendokumentasikan paginasi kursor sebagai kursor buram; materi Hello Interview dan TechInterview menekankan stabilitas kursor/keyset pada dataset yang berubah.
Kriteria keluar pilot
Pengujian penyisipan/penghapusan konkuren memenuhi semantik duplikat dan penghilangan yang dideklarasikan; p95 halaman dalam stabil; manipulasi dan perubahan filter ditolak; klien pulih dengan aman ke halaman satu; dan peninjauan otorisasi mengonfirmasi bahwa token tidak membocorkan data.
Cara membuktikan peningkatannya nyata
Pada ukuran data dan tingkat penulisan yang sama, bandingkan latensi p95/p99 offset dan kursor, baris yang dipindai, tingkat duplikat, tingkat kelalaian, dan CPU basis data. Pisahkan efek cache sehingga kueri dingin (cold query) tunggal tidak menentukan hasilnya.
Kesalahan umum dan pertanyaan lanjutan
Memperlakukan Base64 sebagai kursor yang aman
Base64 adalah pengkodean. Klien dapat mengubah id atau tenant. Tandatangani atau simpan token dan ikat filter, versi, serta kedaluwarsa.
Menyortir hanya berdasarkan stempel waktu
Banyak baris dapat memiliki milidetik yang sama, membuat batasannya ambigu. Tambahkan pemecah seri yang unik dan indeks komposit yang cocok.
Bisakah kursor melompat ke halaman mana saja?
Kursor standar tidak dirancang untuk lompatan arbitrer. Tawarkan offset terbatas, anchor yang dihitung sebelumnya, atau penomoran halaman mesin pencari dengan konsistensi dan biaya eksplisit.
Bisakah pembaruan menduplikasi baris?
Jika bidang sortir berubah, baris dapat berpindah antar halaman. Gunakan urutan pembuatan yang tidak dapat diubah atau semantik snapshot/versi dan dokumentasikan perilakunya.
Bagaimana cara menerapkan tombol halaman sebelumnya?
Simpan tumpukan kursor sebelumnya atau jalankan kueri terbalik dan balikkan hasilnya di server. Klien tidak boleh menyimpulkan struktur internal kursor.
Haruskah jumlah total persis dikembalikan?
Tidak. Infinite scroll biasanya hanya membutuhkan has_more; hitungan persis dapat bersifat asinkron atau terpisah sehingga setiap halaman tidak memindai tabel.