Topik wawancara representatif

Wawancara Backend: Bagaimana Anda Merancang Paginasi Kursor yang Konsisten di Bawah Penulisan Konkuren?

BackendSedang
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Daftar pesanan dengan infinite-scroll menampilkan duplikat atau melewati item saat pengguna berpindah halaman. Data masih terus ditulis. Bagaimana Anda akan merancang API dan kueri basis data?

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

sql
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

PersyaratanPilihanBiaya
Infinite scroll besarKursor keysetTidak ada lompatan halaman arbitrer
Tabel admin kecilOffsetHalaman dalam lambat dan tidak stabil di bawah penulisan
Konsistensi mutlakBatasan snapshotPenyimpanan dan pembersihan snapshot
Hitungan persisHitungan terpisah atau asinkronKerja 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.

Sumber publik

Pertanyaan terkait