Topik wawancara representatif

Wawancara backend: Bagaimana Anda memigrasikan pengidentifikasi UUIDv4 ke UUIDv7 tanpa downtime?

BackendSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Layanan multi-region menggunakan primary key acak UUIDv4 dan mengalami masalah lokalitas indeks serta biaya paginasi yang tinggi. Rancang migrasi bertahap ke uuidv7() pada PostgreSQL 18 tanpa memblokir proses tulis atau merusak fungsionalitas klien. Jelaskan bentuk kunci, dual write, backfill, foreign key, pengurutan, replikasi, validasi, dan rollback.

Konteks dan cakupan

Layanan ini memiliki tabel orders yang besar, banyak tabel anak, URL publik, muatan event, dan replika. PostgreSQL 18 menyediakan generator uuidv7() bawaan untuk UUID yang terurut berdasarkan timestamp. Alihkan data baru ke UUIDv7 sembari mempertahankan kontrak UUIDv4 yang ada dan menghindari penulisan ulang tabel (table rewrite) atau penguncian pemblokiran (blocking lock) yang berlangsung lama.

Ini adalah pertanyaan backend karena keahlian intinya adalah migrasi identitas dan skema daring (online) di seluruh API, penyimpanan, dan konsumen asinkron.

Hal yang dinilai oleh pewawancara

Pertama, apakah Anda dapat membedakan format pengidentifikasi dari kebenaran kronologis? UUIDv7 menyematkan urutan waktu, tetapi ini bukanlah urutan commit global yang ketat dan tidak boleh menggantikan timestamp bisnis yang eksplisit.

Kedua, dapatkah Anda mempertahankan identitas publik? URL, cache, kunci idempotensi, event, dan foreign key yang sudah ada harus tetap dapat diakses dan diresolusi selama migrasi.

Ketiga, dapatkah Anda merancang fase dual-read dan dual-write dengan sumber kebenaran (source of truth) yang tidak ambigu?

Keempat, dapatkah Anda melakukan backfill dengan aman di seluruh replika dan region tanpa menimbulkan amplifikasi penulisan atau jeda replikasi (replication lag)?

Kelima, dapatkah Anda memvalidasi lokalitas dan latensi alih-alih berasumsi bahwa UUIDv7 secara otomatis lebih cepat untuk setiap beban kerja?

Pertanyaan klarifikasi awal

  • Apakah UUID diekspos secara eksternal, digunakan sebagai primary key, atau keduanya?
  • Bisakah klien menerima pengidentifikasi baru, atau apakah UUID lama harus tetap menjadi kanonikal?
  • Berapa banyak tabel anak, indeks, event, dan dokumen pencarian yang mereferensikan kunci tersebut?
  • Versi PostgreSQL dan ekstensi apa saja yang ada pada setiap writer dan replika?
  • Berapa tingkat penulisan, SLO jeda replikasi, dan batas waktu rollback?
  • Apakah pengurutan diperlukan untuk paginasi, tampilan audit, atau hanya lokalitas indeks?

Kerangka jawaban 30 detik

"Saya akan mempertahankan UUIDv4 sebagai pengidentifikasi publik yang stabil, menambahkan kolom pengganti (surrogate) atau pemetaan UUIDv7, dan meluncurkan dual write sebelum melakukan backfill. Pembacaan menerima salah satu kunci selama pemetaan selesai; referensi anak dan event tetap kompatibel. Lakukan backfill dalam rentang primary key yang dibatasi dengan pemantauan jeda dan penguncian, lalu alihkan indeks internal dan jalur kueri setelah pemeriksaan paritas. UUIDv7 membantu lokalitas dan pemindaian berorientasi waktu, tetapi timestamp tetap eksplisit. Rollback dilakukan melalui pembalikan feature flag sampai semua konsumen terbukti stabil."

Jawaban langkah demi langkah

Langkah 1: Pilih bentuk kompatibilitas

Jangan mengubah arti dari UUID yang ada secara diam-diam. Pertahankan order_id_v4 sebagai kontrak eksternal dan tambahkan order_id_v7 beserta batasan unik (unique constraint), atau perkenalkan kunci internal terpisah dengan pemetaan yang tahan lama (durable). Tentukan kunci mana yang digunakan oleh tabel anak dan event selama setiap fase.

sql
ALTER TABLE orders ADD COLUMN order_id_v7 uuid;
CREATE UNIQUE INDEX CONCURRENTLY orders_order_id_v7_uq
  ON orders(order_id_v7) WHERE order_id_v7 IS NOT NULL;

Gunakan uuidv7() hanya pada versi PostgreSQL yang menyediakannya; jika tidak, terapkan generator yang setara secara konsisten sebelum mengaktifkan jalur tersebut.

Langkah 2: Luncurkan penulisan terlebih dahulu

Penulisan baru menghasilkan kedua pengidentifikasi dalam satu transaksi dan mencatat pemetaannya. Penulisan yang sudah ada yang tidak dapat mengisi kolom baru akan tetap valid. Buat proses percobaan ulang (retry) menjadi idempoten sehingga perintah yang berulang tidak dapat membuat dua pemetaan.

Langkah 3: Tambahkan resolusi dual-read

Pencarian API menerima salah satu pengidentifikasi, meresolusikannya ke satu pesanan kanonikal, dan memancarkan ID lama dalam respons lama. Titik akhir (endpoint) baru dapat mengekspos UUIDv7 di balik kontrak berversi. Kunci cache harus menyertakan identitas kanonikal yang telah diresolusi sehingga kedua bentuk tersebut tidak berbeda.

Langkah 4: Lakukan backfill dalam batch yang dibatasi

Lakukan backfill baris menggunakan kursor yang stabil, transaksi kecil, dan jeda saat jeda replikasi, waktu tunggu kunci, atau latensi penulisan melewati batas. Buat indeks pada kolom baru setelah data yang tersedia mencukupi, menggunakan operasi konkuren jika didukung. Jangan memperbarui tabel anak sampai pemetaan induk bersifat tahan lama.

Langkah 5: Migrasikan referensi dan event

Pertahankan foreign key anak dan skema event agar tetap kompatibel selama masa transisi. Tambahkan field v7 sebagai opsional, publikasikan kedua nilai, dan perbarui konsumen sebelum mewajibkan v7. Rekonsiliasi pemetaan yang hilang dan pemetaan duplikat sebelum beralih batasan.

Langkah 6: Alihkan jalur akses internal

Setelah pemeriksaan paritas, arahkan join internal dan paginasi ke UUIDv7 jika lokalitas atau pemindaian berorientasi waktu diperlukan. Pertahankan created_at eksplisit untuk pengurutan bisnis dan gunakan pemutus seri (tie-breaker); pengurutan UUIDv7 bersifat perkiraan di tingkat aplikasi.

Langkah 7: Validasi dan pantau

Bandingkan ukuran indeks, pembagian halaman (page splits), perilaku cache, latensi insert, latensi pemindaian rentang (range-scan), jeda replikasi, dan tingkat kesalahan terhadap beban kerja yang cocok. Periksa bahwa pencarian v4 dan v7 mengembalikan baris yang sama dan konsumen event tetap idempoten.

Langkah 8: Hentikan penggunaan hanya setelah jendela rollback selesai

Pertahankan pemetaan, indeks lama, dan jalur dual-read sampai setiap klien, alat pemutaran ulang (replay), ekspor, dan replika telah melewati jendela migrasi. Hapus batasan lama dalam penerapan (deploy) terpisah dengan rencana rollback yang eksplisit.

Contoh jawaban

"Saya tidak akan menulis ulang kontrak UUID publik secara langsung di tempat. Saya akan menambahkan kolom UUIDv7 dan pemetaan unik, menerapkan dual write, lalu dual read yang meresolusikan salah satu bentuk ke pesanan yang sama. Lakukan backfill dalam transaksi yang dibatasi sambil memantau penguncian, jeda replikasi, dan latensi penulisan. Referensi anak dan event membawa kedua ID sampai setiap konsumen memahami v7. Setelah pengukuran paritas dan lokalitas, alihkan join internal dan paginasi sembari mempertahankan created_at sebagai field pengurutan bisnis. Feature flag dapat membalikkan pembacaan dan penulisan sampai jalur lama benar-benar dipensiunkan."

Kesalahan umum

  • Mengganti UUID publik secara langsung → tautan dan event rusak → pertahankan kontrak kanonikal dan pemetaan.
  • Mengasumsikan UUIDv7 adalah urutan waktu yang ketat → paginasi melompati atau mengurutkan ulang data secara keliru → gunakan timestamp eksplisit dan pemutus seri.
  • Melakukan backfill dalam satu transaksi raksasa → penguncian dan jeda replikasi melonjak drastis → gunakan batch yang dibatasi.
  • Menambahkan foreign key sebelum pemetaan ada → proses tulis gagal selama masa transisi → migrasikan induk, anak, lalu batasan.
  • Menghasilkan ID pada versi campuran yang tidak didukung → semantik menjadi berbeda → kunci versi atau gunakan satu generator yang konsisten.
  • Mengabaikan percobaan ulang (retry) → muncul pemetaan duplikat → buat dual write menjadi idempoten.
  • Menghapus v4 terlalu cepat → ekspor lama dan proses replay gagal → tunggu hingga jendela rollback selesai.

Pertanyaan lanjutan

Pertanyaan lanjutan 1: Apakah UUIDv7 merupakan pengganti untuk created_at?

Tidak. UUIDv7 dapat meningkatkan lokalitas dan menyediakan bit yang berorientasi timestamp, tetapi pengurutan bisnis tetap membutuhkan timestamp eksplisit dan pemutus seri yang deterministik.

Pertanyaan lanjutan 2: Bisakah v4 dan v7 berbagi satu kolom UUID yang sama?

Ya, tipe data UUID dapat menampung kedua format, tetapi metadata migrasi, kontrak eksternal, dan semantik pengurutan tetap memerlukan rencana yang eksplisit.

Pertanyaan lanjutan 3: Mengapa dual-read dilakukan sebelum mengalihkan penulisan?

Hal ini memungkinkan data lama dan baru diresolusikan secara konsisten saat backfill dan peluncuran pada konsumen belum selesai.

Pertanyaan lanjutan 4: Bagaimana cara membatasi laju (throttle) backfill?

Gunakan transaksi yang dibatasi dan beri jeda saat mencapai batas jeda replikasi, waktu tunggu kunci, CPU, atau latensi penulisan; lanjutkan kembali dari kursor yang tahan lama.

Pertanyaan lanjutan 5: Apa yang harus termuat dalam event?

Selama masa transisi, publikasikan kedua ID atau referensi pemetaan yang stabil, beri versi pada skema, dan perbarui konsumen sebelum mewajibkan v7.

Pertanyaan lanjutan 6: Kapan kolom lama dapat dihapus?

Hanya setelah klien, ekspor, replay, replika, dan pemeriksaan rollback melewati jendela yang disepakati; hapus batasan dan indeks dalam penerapan terpisah yang dapat dibatalkan (reversible).

Sumber publik

Pertanyaan terkait