Gesaan dan konteks
Soalan ini menguji sama ada jurutera backend menganggap penomboran halaman sebagai protokol bacaan yang stabil. Pesanan, komen dan log boleh disisipkan, dipadamkan atau dikemas kini antara permintaan. OFFSET biasa bergantung pada kedudukan yang berganjak dan mengimbas banyak baris pada halaman yang dalam. Rangkumi pengisihan, pengekodan kursor, pengikatan penapis, sempadan ketekalan, indeks, serta semantik ke hadapan/ke belakang.
Perkara yang diuji oleh penemu duga
Jawapan yang kukuh menjelaskan sama ada produk memerlukan lompatan, pengiraan jumlah atau hasil masa nyata, kemudian memilih keyset/kursor atau offset. Kursor mengikat pengisihan dan penapis; kunci isihannya adalah unik, stabil dan diindekskan. Pelayan menandatanganinya dan menetapkan tarikh luput. Jawapan menerangkan sebab baris baharu tidak menduplikasi halaman seterusnya, bagaimana pemadaman mempengaruhi hasil, dan bagaimana next_cursor serta status akhir senarai dikembalikan.
Soalan untuk penjelasan
- Apakah tertib isihan? Bolehkah medan isihan berubah, dan apakah pemutus seri (tie-breaker) yang unik?
- Adakah kita memerlukan navigasi halaman sebelumnya, lompatan halaman rawak, kiraan tepat, atau skrol tak terhingga ke hadapan sahaja?
- Patutkah bacaan menggunakan snapshot beku atau membenarkan senarai langsung yang konsisten akhirnya (eventually consistent)?
- Adakah penapis, skop penyewa (tenant), kebenaran dan tertib isihan mesti diikat ke dalam kursor? Berapa lamakah ia sah?
- Bagaimanakah pemadaman, pemadaman lembut (soft delete), perubahan kebenaran dan bacaan merentas syard dikendalikan?
Rangka kerja jawapan 30 saat
"Saya akan menggunakan kunci komposit yang stabil seperti (created_at, id) untuk pertanyaan keyset menurun. Kursor ialah token legap (opaque) yang ditandatangani yang mengandungi nilai isihan terakhir, hash penapis, arah dan versi. Pelayan mengesahkannya dan menjalankan WHERE (created_at,id) < (:time,:id) terhadap indeks komposit yang sepadan. Baris baharu muncul semasa muat semula dan bukannya disisipkan ke dalam tetingkap yang telah dibaca; pemadaman mungkin memendekkan halaman tetapi tidak menghasilkan duplikasi. Jika ketekalan mutlak diperlukan, saya akan menambah sempadan snapshot dan menerangkan kosnya."
Jawapan mendalam langkah demi langkah
Langkah 1: Tentukan semantik senarai
Tentukan sama ada ini ialah sejarah audit, suapan langsung atau jadual pentadbir. Sejarah biasanya memerlukan sempadan yang stabil; suapan langsung mungkin menunjukkan baris baharu hanya selepas muat semula. Jangan menjanjikan kemas kini masa nyata, lompatan rawak, kiraan tepat dan kos rendah secara serentak.
Langkah 2: Pilih kunci isihan yang stabil
Cap masa boleh mempunyai nilai seri atau diedit, jadi tambahkan id unik sebagai pemutus seri. Elakkan medan paparan. Jika kemas kini boleh memindahkan rekod, gunakan jujukan penciptaan yang tidak boleh diubah (immutable) atau dokumentasikan secara eksplisit bahawa baris boleh berpindah antara halaman.
Langkah 3: Reka bentuk kursor legap (opaque cursor)
Sertakan nilai isihan, arah, hash penapis, versi API dan tarikh luput. Tandatanganinya atau simpannya di bahagian pelayan; Base64 hanyalah pengekodan, bukan keselamatan. Tolak kursor apabila penapis berubah daripada mengembalikan halaman yang tidak berkaitan secara senyap.
Langkah 4: Tulis pertanyaan keyset
Untuk (created_at,id) menurun, halaman seterusnya menggunakan created_at < t OR (created_at = t AND id < id0) dengan indeks komposit yang sama. Parameterkan pertanyaan, hadkan limit, dan jangan sekali-kali mencantumkan nilai kursor ke dalam SQL.
Langkah 5: Kendalikan penulisan dan pemadaman
Baris yang disisipkan selepas halaman pertama tidak sepatutnya muncul di halaman kedua; ia muncul selepas muat semula. Baris yang dipadamkan boleh menjadikan halaman lebih pendek, yang merupakan semantik diisytiharkan yang boleh diterima. Jika peninggalan data tidak boleh diterima, gunakan sempadan snapshot atau bacaan berversi.
Langkah 6: Ikat kebenaran dan penapis
Hash penapis, penyewa dan skop kebenaran kursor mesti sepadan dengan permintaan. Kira semula hasil apabila kebenaran diperketatkan; kursor lama tidak boleh memintas kawalan akses. Penyelaras merentas syard boleh menggabungkan kursor tempatan, tetapi jawapan mesti menyatakan kos amplifikasi.
Langkah 7: Tentukan respons dan ralat
Kembalikan items, next_cursor, has_more, dan secara pilihan id snapshot. Kursor yang tamat tempoh atau tidak sah serta penapis yang diubah menggunakan ralat perniagaan yang stabil; klien mengosongkan kursor dan memulakan semula dari halaman satu. Jangan dedahkan SQL atau kegagalan dalaman pangkalan data.
Langkah 8: Sahkan dengan ujian keserentakan
Uji sisipan, pemadaman, cap masa yang sama, perubahan penapis, kursor yang diusik dan halaman dalam antara permintaan. Sahkan tiada duplikasi untuk satu sesi, pencarian indeks (index seeks), dan kependaman yang tidak meningkat secara linear dengan nombor halaman. Jejaki kadar duplikasi, kadar langkauan, kependaman p95 dan ralat kursor.
Pseudokod pertanyaan
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 sempadan
| Keperluan | Pilihan | Kos |
|---|---|---|
| Skrol tak terhingga besar | Kursor keyset | Tiada lompatan halaman rawak |
| Jadual pentadbir kecil | Offset | Halaman dalam perlahan dan tidak stabil di bawah penulisan |
| Ketekalan mutlak | Sempadan snapshot | Penyimpanan dan pembersihan snapshot |
| Kiraan tepat | Kiraan berasingan atau tak segerak | Kerja tambahan dan kemungkinan data lapuk (staleness) |
Penomboran halaman kursor menyelesaikan kestabilan kedudukan dan kecekapan pertanyaan; ia tidak menyelesaikan deduplikasi perniagaan merentas halaman, perubahan kebenaran atau kemas kini yang memindahkan baris secara automatik. Hasil carian juga memerlukan versi pertanyaan; agregat mungkin memerlukan bacaan titik masa (point-in-time read).
Pelan pelancaran dan bukti
Pilih senarai bacaan tinggi dan ukur duplikasi semasa, langkauan, kependaman halaman dalam dan kos kiraan. Tambahkan covering index, keluarkan kursor berversi, dan tambahkan ujian serta metrik penulisan serentak. Django REST framework mendokumentasikan penomboran halaman kursor sebagai kursor legap; bahan Hello Interview dan TechInterview menekankan kestabilan kursor/keyset pada set data yang berubah.
Kriteria keluar perintis
Ujian sisipan/pemadaman serentak memenuhi semantik duplikasi dan peninggalan yang diisytiharkan; p95 halaman dalam adalah stabil; pengusikan dan perubahan penapis ditolak; klien pulih dengan selamat ke halaman satu; dan semakan kebenaran mengesahkan token tidak membocorkan data.
Cara membuktikan peningkatan adalah nyata
Pada saiz data dan kadar penulisan yang sama, bandingkan kependaman p95/p99 offset dan kursor, baris yang diimbas, kadar duplikasi, kadar peninggalan dan CPU pangkalan data. Asingkan kesan cache supaya satu pertanyaan sejuk (cold query) tidak menentukan hasilnya.
Kesilapan lazim dan susulan
Menganggap Base64 sebagai kursor selamat
Base64 adalah pengekodan. Klien boleh mengubah id atau penyewa. Tandatangani atau simpan token dan ikat penapis, versi serta tarikh luput.
Menyusun mengikut cap masa sahaja
Banyak baris boleh berkongsi milisaat yang sama, menjadikan sempadan kabur. Tambahkan pemutus seri yang unik dan indeks komposit yang sepadan.
Bolehkah kursor melompat ke mana-mana halaman?
Kursor standard tidak direka untuk lompatan rawak. Tawarkan offset terikat, sauh (anchors) yang dikira terlebih dahulu, atau penomboran halaman enjin carian dengan ketekalan dan kos yang jelas.
Bolehkah kemas kini menduplikasi baris?
Jika medan isihan berubah, baris boleh berpindah antara halaman. Gunakan jujukan penciptaan yang tidak boleh diubah atau semantik snapshot/versi dan dokumentasikan tingkah laku tersebut.
Bagaimanakah anda melaksanakan butang halaman sebelumnya?
Simpan tindanan kursor sebelumnya atau jalankan pertanyaan songsang dan balikkan hasilnya pada pelayan. Klien tidak sepatutnya membuat inferens terhadap struktur dalaman kursor.
Adakah jumlah kiraan tepat mesti dikembalikan?
Tidak. Skrol tak terhingga biasanya hanya memerlukan has_more; kiraan tepat boleh dibuat secara tak segerak atau berasingan supaya setiap halaman tidak mengimbas jadual.