Topik wawancara representatif

Bagaimana Cara Mendesain API Cursor Pagination yang Stabil?

BackendSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Sebuah tabel pesanan memiliki 200 juta baris dan penulisan yang berkelanjutan, diurutkan berdasarkan created_at DESC, id DESC. Rancang API yang mengembalikan 50 baris per halaman, mendukung navigasi maju dan mundur, menghindari penurunan performa pada paginasi dalam (deep pagination), serta menyatakan jaminannya di bawah operasi insert, delete, dan perubahan status yang terjadi secara serentak.

Masalah dan Cakupan

Layanan pesanan multi-tenant menggunakan PostgreSQL. Tabel orders miliknya berisi 200 juta baris dan menerima operasi insert serta perubahan status secara terus-menerus. Konsol operasional memfilter pesanan berdasarkan tenant dan status, selalu mengurutkan berdasarkan created_at DESC, id DESC, dan mengembalikan paling banyak 50 baris per halaman. Baik created_at maupun id bernilai non-null dan tidak dapat diubah (immutable) setelah dibuat; id bersifat unik. Produk memerlukan navigasi halaman berikutnya dan halaman sebelumnya, tetapi tidak memerlukan lompatan langsung ke halaman 500.

Rancang API paginasi, payload kursor, kueri, dan indeks. Desain harus mampu menangani timestamp pembuatan yang identik, paginasi dalam (deep pagination), insert dan delete di antara request, perubahan keanggotaan status, manipulasi kursor (cursor tampering), serta navigasi di kedua arah. Desain juga harus membedakan antara penelusuran data langsung (live traversal) dan snapshot yang tetap.

Data 200 juta baris dan ukuran halaman 50 baris adalah asumsi wawancara. Keterampilan intinya adalah menerjemahkan semantik API ke dalam jalur akses database, batas konkurensi, dan rencana verifikasi, sehingga ini adalah pertanyaan backend. Replikasi lintas-wilayah, sharding, dan state daftar di sisi klien berada di luar cakupan putaran pertama.

Apa yang Dievaluasi oleh Pewawancara

Sinyal pertama adalah apakah kandidat mendefinisikan konsistensi sebelum memilih algoritma. "Tidak ada duplikasi atau data yang terlewat" membutuhkan objek yang jelas: apakah ini berarti menghindari pergeseran data yang disebabkan oleh offset pagination, atau apakah seluruh sesi penjelajahan harus berperilaku seperti satu snapshot database? Kursor biasa dapat melanjutkan data setelah batas pengurutan; kursor tidak membekukan operasi delete, perubahan status, atau field filter lainnya.

Sinyal kedua adalah total order. Jika kueri hanya mengurutkan berdasarkan created_at, beberapa pesanan mungkin memiliki nilai yang sama dan database dapat mengaturnya secara sembarangan. id yang unik sebagai kunci pengurutan kedua, dengan kedua nilai disertakan dalam kursor, secara tepat mengidentifikasi posisi setelah baris terakhir pada halaman sebelumnya.

Sinyal ketiga adalah apakah kueri, indeks, dan kursor menerapkan urutan yang sama. tenant_id dan status adalah filter kesetaraan (equality filters). created_at dan id membentuk batas rentang dan urutan sortir. Oleh karena itu, indeks B-tree kandidat adalah (tenant_id, status, created_at DESC, id DESC). Mengganti kata "offset" dengan "kursor" tidaklah lengkap tanpa predikat perbandingan, indeks, dan kueri terbalik (reverse query).

Terakhir, pewawancara ingin mendengar kontrak yang konkret dan rencana pengujian. Jawaban yang kuat mengikat filter dan cakupan otorisasi ke dalam kursor yang ditandatangani (signed cursor), membatasi ukuran halaman maksimum, serta menguji operasi insert serentak, penghapusan baris batas, timestamp yang sama, dan navigasi bolak-balik maju/mundur. Jawaban tersebut juga mempertahankan offset pagination sebagai opsi yang valid untuk dataset kecil dan stabil yang memerlukan lompatan nomor halaman, alih-alih menganggap satu teknik selalu benar untuk semua situasi.

Pertanyaan Klarifikasi

  • Apakah ini daftar langsung (live list) atau snapshot tetap? Daftar langsung dapat mencerminkan beberapa perubahan pada halaman-halaman berikutnya. Snapshot tetap mengharuskan penelusuran melihat satu set data yang sama, biasanya melalui data berversi (versioned data), hasil termaterialisasi (materialized result), atau mekanisme snapshot yang bertahan di berbagai request. Biaya dan kebijakan kedaluwarsanya berbeda.
  • Haruskah pengguna dapat melompat ke sembarang halaman? Keyset pagination sangat baik untuk navigasi berurutan ke depan dan ke belakang, tetapi "halaman 500" saja tidak mengungkapkan batas kunci (key boundary). Jika lompatan halaman wajib ada, pertahankan offset untuk kedalaman terbatas atau hitung anchor terlebih dahulu.
  • Bisakah field pengurutan atau filter berubah? created_at dan id harus tetap stabil. Mengurutkan berdasarkan updated_at yang dapat berubah, harga, atau skor memungkinkan sebuah rekaman melintasi batas yang sudah dikunjungi. Perubahan status juga mengubah keanggotaan hasil dan harus menjadi bagian dari kontrak publik.
  • Apakah produk memerlukan jumlah total yang tepat dan halaman terakhir? Mengambil satu baris ekstra dapat menentukan hasNextPage; totalCount yang tepat membutuhkan biaya agregasi tersendiri. Antarmuka load-more sebaiknya tidak memaksakan full count pada setiap halaman.
  • Berapa lama kursor boleh tetap valid? Perubahan skema, perubahan aturan filter, rotasi signing-key, dan retensi snapshot semuanya memengaruhi validitas. Server memerlukan error kedaluwarsa dan cara eksplisit untuk memulai kembali dari halaman pertama.
  • Bisakah akses tenant atau izin berubah saat melakukan paging? Kursor tidak pernah menggantikan autentikasi dan otorisasi untuk request saat ini. Setiap halaman memeriksa ulang principal saat ini, dan kursor diikat ke filter yang dinormalisasi dan cakupan akses sehingga tidak dapat digunakan kembali di seluruh tenant yang berbeda.

Jawaban 30 Detik

"Pertama, saya akan memastikan bahwa ini adalah penelusuran terurut secara langsung (live ordered traversal), bukan snapshot database lintas request. Saya akan menggunakan created_at DESC, id DESC yang immutable, dengan ID unik sebagai penentu jika ada timestamp yang bernilai sama. Kursor halaman berikutnya membawa dua nilai terakhir dari halaman sebelumnya, sebuah versi, dan sidik jari filter (filter fingerprint), lalu menggunakan encoding yang aman untuk URL (URL-safe encoding) dan tanda tangan HMAC. Kueri menerapkan (created_at, id) < (cursor_time, cursor_id), urutan menurun yang sama, dan LIMIT 51; kueri mengembalikan 50 baris dan menggunakan baris ekstra untuk hasNextPage. Indeks yang cocok adalah (tenant_id, status, created_at DESC, id DESC). Untuk halaman sebelumnya, saya membalikkan perbandingan dan urutan database, lalu membalikkan hasilnya sebelum dikirim. Insert baru tidak akan menggeser halaman berikutnya, tetapi delete dan perubahan status tidak dibekukan. Snapshot yang ketat memerlukan data berversi atau sesi termaterialisasi. Saya akan menguji timestamp yang bernilai sama, penulisan serentak, penghapusan data batas, manipulasi kursor, dan rencana eksekusi halaman dalam."

Solusi Langkah demi Langkah

Mulailah dengan mendefinisikan kontrak API. Halaman pertama tidak memiliki kursor. Navigasi maju menerima after; navigasi mundur menerima before; sebuah request tidak boleh memuat keduanya. Ukuran halaman default adalah 50, dengan rentang 1 hingga 100 yang diberlakukan oleh server. Respons dapat menggunakan struktur berikut:

json
{
  "data": [],
  "pageInfo": {
    "startCursor": "...",
    "endCursor": "...",
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

startCursor dan endCursor berasal dari baris pertama dan terakhir yang dikembalikan. Minta 51 baris dan hanya kembalikan 50 baris untuk menentukan hasNextPage. Jika flag arah berlawanan harus tepat, jalankan kueri EXISTS kecil yang terindeks dari batas lainnya. Menyimpulkannya hanya dari keberadaan parameter after bisa salah setelah semua baris sebelumnya dihapus.

Mengganti Offset Posisi dengan Batas Majemuk (Compound Boundary)

Kueri halaman pertama adalah:

sql
SELECT id, created_at, total_cents, status
FROM orders
WHERE tenant_id = $1
  AND status = $2
ORDER BY created_at DESC, id DESC
LIMIT 51;

Untuk halaman berikutnya, tempatkan pasangan (created_at, id) terakhir dari halaman sebelumnya dalam predikat 'kurang dari' yang ketat (strict less-than predicate):

sql
SELECT id, created_at, total_cents, status
FROM orders
WHERE tenant_id = $1
  AND status = $2
  AND (created_at, id) < ($3, $4)
ORDER BY created_at DESC, id DESC
LIMIT 51;

PostgreSQL membandingkan row constructor dari kiri ke kanan dan berhenti pada pasangan pertama yang tidak sama. Ini cocok dengan batas leksikografis dari dua sort key menurun tersebut. Mendefinisikan kedua kolom sebagai non-null menghindari hasil unknown yang dapat dihasilkan oleh perbandingan baris saat bertemu NULL. id tidak harus mengodekan waktu; kolom ini hanya perlu menyediakan pemecah seri (tie-break) yang unik dan stabil dalam satu nilai created_at.

Indeks yang cocok adalah:

sql
CREATE INDEX CONCURRENTLY orders_tenant_status_created_id_idx
ON orders (tenant_id, status, created_at DESC, id DESC);

Kolom kesetaraan di posisi awal membatasi pemindaian hanya pada satu tenant dan status. Dua kolom terakhir menyediakan batas dan urutan. Apakah field respons perlu ditambahkan sebagai included column bergantung pada lebar baris yang diukur, heap fetch, dan write amplification; menyalin setiap field respons ke dalam INCLUDE bukanlah pilihan default. Perubahan status yang sering juga akan memperbarui indeks ini, jadi ukur latensi penulisan, volume WAL, dan ukuran indeks.

Membuat Kursor Dapat Berevolusi dan Kebal Manipulasi (Tamper-Evident)

Kursor adalah protokol buram (opaque protocol), bukan permintaan bagi klien untuk menyusun nilai created_at. Payload logisnya dapat berupa:

json
{
  "v": 1,
  "createdAt": "2026-07-16T02:30:00.123Z",
  "id": "ord_01J...",
  "filterHash": "sha256:...",
  "expiresAt": "2026-07-17T02:30:00Z"
}

Server menerapkan URL-safe encoding ke payload kanonikal dan melampirkan HMAC. Base64 hanyalah encoding, bukan enkripsi/kerahasiaan (confidentiality). Jika nilai batas bersifat sensitif, jangan ekspos nilai tersebut dalam token klien; simpanlah di sisi server di bawah ID kursor acak. filterHash harus mencakup setidaknya versi pengurutan, filter status, dan pengidentifikasi cakupan otorisasi. Setiap request diotorisasi terhadap principal saat ini sebelum server memverifikasi tanda tangan, versi, masa kedaluwarsa, dan sidik jari filter. Setiap ketidakcocokan akan mengembalikan respons invalid_cursor yang stabil dan mengharuskan memulai ulang dari halaman pertama; server tidak boleh secara diam-diam menerapkan kursor tersebut ke filter baru.

Field versi memungkinkan perubahan pengurutan atau encoding di masa mendatang. Selama rotasi kunci, pemverifikasi dapat menerima kunci verifikasi saat ini dan sebelumnya selama masa aktif maksimum kursor, sementara penandatanganan kursor baru hanya menggunakan kunci saat ini. Log mencatat kategori error dan versi kursor, bukan seluruh kursor atau filter dalam bentuk teks polos.

Mengimplementasikan Halaman Sebelumnya Secara Simetris

Baris pertama halaman saat ini adalah batas untuk bernavigasi ke rekaman yang lebih baru. Ubah predikat menjadi > dan buat kueri dalam urutan menaik (ascending) sehingga database terlebih dahulu mengembalikan 50 baris terdekat dari batas tersebut:

sql
SELECT id, created_at, total_cents, status
FROM orders
WHERE tenant_id = $1
  AND status = $2
  AND (created_at, id) > ($3, $4)
ORDER BY created_at ASC, id ASC
LIMIT 51;

Setelah menghapus baris probe ke-51, server membalikkan hasilnya dan mengembalikan urutan menurun publik. Mempertahankan urutan menurun dengan LIMIT akan memilih 50 baris pertama di seluruh set hasil, bukan halaman yang tepat sebelum batas tersebut. Kedua arah harus berbagi sidik jari filter dan versi pengurutan yang sama.

Menyatakan Jaminan di Bawah Perubahan Serentak

Di bawah semantik live, kursor berarti "lanjutkan dengan baris-baris yang berada tepat di bawah batas immutable ini." Jika 100 pesanan baru masuk setelah pengguna membaca halaman satu, halaman dua tetap dilanjutkan dari batas lama. Baris-baris baru tidak mendorong baris terakhir halaman satu ke halaman dua. Menghapus baris batas setelahnya juga tidak merusak penelusuran karena kursor menyimpan nilainya; kueri tidak perlu menemukan baris itu lagi.

Ini bukan snapshot tetap. Pesanan yang belum dibaca yang berubah dari status lain menjadi status terpilih dapat muncul nanti jika posisi urutannya berada di bawah batas saat ini. Pesanan yang keluar dari kriteria filter tidak akan muncul. Penghapusan data dapat mengurangi baris yang terlihat oleh penelusuran. Menempatkan "first-page start time" di kursor hanya membatasi insert yang lebih baru; ini tidak membekukan delete atau perubahan status.

Jika proses ekspor, rekonsiliasi, atau audit harus memuat setiap anggota dari satu set data yang tetap, salah satu opsinya adalah hasil termaterialisasi berumur pendek yang menyimpan ID pesanan yang cocok dan urutannya. Opsi lain adalah membaca data berversi dengan dukungan time-travel. Background job yang terkontrol juga dapat mempertahankan satu snapshot database. Setiap pilihan menambah biaya penyimpanan, masa aktif transaksi, atau pembersihan (cleanup) dan memerlukan masa kedaluwarsa sesi. Penelusuran keyset langsung biasanya cocok untuk daftar interaktif; beban kerja audit memerlukan alur kerja snapshot tersendiri.

Menjaga Offset Pagination Tetap pada Batas Kegunaannya

Offset pagination sederhana, mendukung lompatan halaman langsung, dan memetakan nomor halaman secara alami. Ini berfungsi untuk tabel administratif kecil yang stabil atau hasil pencarian yang sudah dibekukan. Biayanya ada dua: PostgreSQL masih harus menghitung dan membuang baris-baris sebelum OFFSET, sehingga offset yang lebih dalam umumnya membutuhkan lebih banyak pekerjaan; operasi insert dan delete yang serentak juga mengubah nomor posisi, yang dapat menyebabkan baris berulang atau terlewat.

Keyset pagination mengaitkan pekerjaan ke batas nilai yang terindeks, sehingga halaman yang dalam tidak memindai dan membuang setiap baris sebelumnya hanya karena angka offset-nya besar. Keyset tidak dapat melompat ke sembarang halaman tanpa anchor. Aturan keputusannya jelas: gunakan keyset untuk penelusuran berurutan yang dalam pada data yang dinamis; gunakan offset untuk navigasi nomor halaman yang terbatas pada data kecil atau beku; tambahkan mekanisme snapshot, terlepas dari gaya paginasi mana pun, ketika seluruh set data harus tetap konstan.

Membuktikan Kontrak dengan Kasus Uji Adversarial

Verifikasi keanggotaan dan urutan data sebelum mengukur latensi:

  1. Masukkan 120 pesanan dengan created_at yang sama. Pastikan id yang unik mencegah duplikasi di seluruh halaman dan penelusuran gabungan benar-benar berurutan menurun.
  2. Baca halaman satu, lalu masukkan 100 pesanan yang lebih baru. Reproduksi pergeseran posisi dengan offset dan pastikan halaman dua keyset tidak mengulangi baris dari halaman satu.
  3. Hapus baris terakhir halaman satu sebelum melanjutkan dan pastikan batas yang tersimpan tetap mengembalikan halaman berikutnya dengan benar.
  4. Ubah status pesanan yang belum dibaca di antara request dan pastikan hasilnya mengikuti semantik live yang didokumentasikan, alih-alih dilaporkan sebagai snapshot.
  5. Ubah satu byte kursor, gunakan kembali di berbagai tenant yang berbeda, ubah filter status, dan kirimkan versi yang sudah kedaluwarsa. Setiap request tersebut harus ditolak.
  6. Bergerak maju tiga halaman dan mundur tiga halaman, periksa ID dan urutannya. Sertakan ukuran hasil 0, 1, 50, dan 51.
  7. Jalankan EXPLAIN (ANALYZE, BUFFERS) untuk halaman pertama, halaman kedua, dan batas yang dalam. Pastikan compound index yang diharapkan digunakan, jumlah baris yang dipindai mendekati ukuran halaman, lalu catat p95 pembacaan, p95 penulisan, volume WAL, dan ukuran indeks.

Contoh Jawaban yang Kuat

"Pertama-tama saya akan membatasi jaminannya. Daftar operasional ini memerlukan penelusuran berurutan langsung, tidak ada lompatan halaman langsung, dan tidak ada snapshot database di beberapa HTTP request. Oleh karena itu, saya akan menggunakan keyset pagination daripada offset.

Urutan yang digunakan adalah created_at DESC, id DESC yang immutable. ID adalah penentu unik jika nilai sama; tanpanya, pesanan yang dibuat pada milidetik yang sama tidak memiliki urutan yang stabil. Saya mengambil 51 baris dan mengembalikan 50 baris. Kursor berikutnya menyimpan timestamp dan ID baris terakhir, dan kueri berikutnya menggunakan (created_at, id) < (?, ?) dengan urutan yang persis sama. Filter kesetaraan ditempatkan lebih dahulu di (tenant_id, status, created_at DESC, id DESC). Untuk halaman sebelumnya, baris pertama saat ini menjadi batasnya; saya menggunakan >, melakukan kueri secara menaik untuk 51 baris terdekat, lalu membalikkan urutan responsnya.

Kursor berisi versi, dua nilai batas, sidik jari filter, dan waktu kedaluwarsa. Kursor tersebut di-encode secara URL-safe dan ditandatangani dengan HMAC. Server mengotorisasi setiap halaman lagi dan menolak kursor yang dimanipulasi, kedaluwarsa, atau tidak cocok filternya. Kursor tidak bergantung pada keberadaan baris batas di database, jadi menghapus baris tersebut tidak akan menghentikan navigasi.

Untuk konsistensi, saya menjamin penghindaran duplikasi yang disebabkan oleh pergeseran offset. Pesanan baru yang dimasukkan setelah halaman satu tidak akan masuk ke halaman-halaman berikutnya, tetapi perubahan status dan penghapusan data masih dapat mengubah set data yang belum dibaca. Jika rekonsiliasi memerlukan snapshot tetap, saya akan membuat sesi ekspor termaterialisasi atau membaca data berversi; menambahkan timestamp ke kursor biasa bukanlah snapshot yang lengkap.

Terakhir, saya akan menguji timestamp yang sama, operasi insert serentak, penghapusan data batas, perubahan status, manipulasi kursor, serta navigasi bolak-balik maju/mundur. Kemudian saya akan membandingkan query execution plan untuk halaman satu, halaman dua, dan batas yang dalam, ditambah efek indeks baru terhadap latensi penulisan dan WAL."

Kesalahan Umum

  • Meng-encode nomor halaman dengan Base64 → Server tetap mengeksekusi OFFSET, sehingga baik performa maupun pergeseran posisi tidak berubah → Jadikan kursor merepresentasikan batas pengurutan yang terindeks.
  • Hanya menggunakan created_at Timestamp yang bernilai sama tidak membentuk total order dan dapat menyebabkan data berulang atau terlewat → Tambahkan id yang unik dan stabil ke kueri dan kursor.
  • Menggunakan predikat yang tidak sesuai dengan urutan sortir → Batas tidak lagi merepresentasikan urutan publik, menciptakan tumpang tindih atau celah data → Turunkan ORDER BY, operator perbandingan, dan indeks dari satu urutan leksikografis yang sama.
  • Menganggap Base64 sebagai perlindungan manipulasi → Klien dapat mengubah timestamp, ID, atau cakupan filter → Gunakan HMAC untuk integritas dan jalankan ulang otorisasi saat ini.
  • Tidak menyertakan filter dalam pengikatan kursor → Menggunakan kembali kursor pesanan berstatus pending untuk kueri pesanan berstatus paid akan menciptakan celah yang membingungkan → Sertakan filter yang dinormalisasi, versi pengurutan, dan cakupan akses dalam sidik jari yang ditandatangani.
  • Tetap menggunakan urutan menurun untuk LIMIT halaman sebelumnya → Kueri mengembalikan bagian awal dari seluruh set data alih-alih segmen yang berada tepat di samping batas → Balikkan urutan database, pangkas, lalu balikkan responsnya.
  • Mengklaim keyset sebagai snapshot yang lengkap → Operasi delete, perubahan status, dan sort key yang dapat berubah tetap mengubah keanggotaan data → Publikasikan semantik live; tambahkan snapshot berversi atau termaterialisasi jika diperlukan konsistensi yang ketat.
  • Menghitung total baris pada setiap halaman → Paginasi menjadi lebih cepat sementara totalCount menjadi jalur lambat yang baru → Perlakukan penghitungan total sebagai kemampuan produk yang terpisah, dapat di-cache, atau perkiraan.
  • Hanya menguji halaman pertama → Batas majemuk baru digunakan pada halaman dua, di mana ketidakcocokan indeks dapat mulai terlihat → Periksa execution plan untuk dua halaman pertama, halaman yang dalam, dan kedua arah navigasi.

Pertanyaan Lanjutan

Bagaimana jika produk harus mengurutkan berdasarkan total_cents yang dapat berubah?

Compound cursor dapat menjadi (total_cents, id), tetapi itu hanya menyelesaikan masalah nilai yang sama. Ini tidak mencegah jumlah yang diedit berpindah melintasi batas yang sudah dikunjungi. Jika pengurutan ulang secara langsung dapat diterima, dokumentasikan kemungkinan duplikasi atau data yang terlewat dan lakukan deduplikasi berdasarkan ID di sisi klien. Jika stabilitas wajib, bekukan nilai sortir untuk sesi penjelajahan tersebut, variasikan daftar ID secara termaterialisasi, atau buat hasil edit menghasilkan versi baru. Jaminan immutable-key tidak berlaku lagi di sini.

Bagaimana jika pengguna harus melompat langsung ke halaman 500?

Keyset pagination tidak mengetahui batas untuk halaman 500. Pertama, tanyakan apakah kebutuhan sebenarnya adalah tanggal, nomor pesanan, atau label halaman; tanggal dan nomor pesanan dapat menjadi anchor pencarian yang terindeks. Jika nomor halaman numerik wajib digunakan, batasi kedalaman lompatan dan gunakan offset di dalam rentang tersebut, atau simpan anchor halaman secara berkala dan lakukan keyset dari anchor terdekat. Anchor juga dapat usang di bawah data live, sehingga memerlukan versi atau pengidentifikasi snapshot.

Bisakah API ini mengekspor seluruh 200 juta baris?

Kursor berumur pendek dan semantik live dari API interaktif tidak cocok untuk ekspor audit jangka panjang. Buat background export job, baca snapshot tetap atau batas versi dalam potongan-potongan primary key yang immutable, tulis ke object storage, dan simpan checkpoint serta hitungan validasi secara persisten. Mekanisme retry, kedaluwarsa, batas sumber daya, dan pemeriksaan penyelesaian kemudian memiliki siklus hidupnya sendiri alih-alih mengikat satu kursor HTTP secara terus-menerus ke indeks online dan format penandatanganan.

Bagaimana cara melakukan rotasi signing key selama paginasi?

Sertakan versi atau pengidentifikasi kunci dalam kursor. Selama masa aktif maksimum kursor, pemverifikasi menyimpan kunci verifikasi saat ini dan sebelumnya, sementara kursor baru hanya ditandatangani dengan kunci saat ini. Hapus kunci lama setelah jendela waktu tersebut berakhir. Jika insiden keamanan mengharuskan pencabutan instan, kembalikan invalid_cursor secara konsisten dan minta klien memulai ulang dari halaman satu alih-alih menerima kunci yang disusupi demi navigasi yang mulus.

Mengapa menghapus baris batas tetap berfungsi sedangkan mengubah sort key-nya tidak?

Kueri berikutnya hanya membutuhkan dua nilai yang disimpan di dalam kursor; kueri tidak perlu menemukan baris batas itu lagi di database, sehingga penghapusan baris tidak mengubah predikat 'kurang dari' yang ketat. Mengedit sort key memindahkan rekaman yang sama dari satu sisi batas ke sisi lainnya. Rekaman tersebut dapat masuk kembali ke rentang mendatang atau melompat dari rentang yang belum dibaca ke rentang yang sudah dikunjungi. Nilai pengurutan yang stabil adalah syarat kebenaran (correctness requirement); keberadaan baris batas secara terus-menerus bukanlah syarat mutlak.

Sumber publik

Pertanyaan terkait