Prompt dan Konteks yang Berlaku
Sebuah tabel PostgreSQL customer_contacts memiliki 200 juta baris. Percobaan ulang (retry) pada pengimpor lama membuat beberapa baris untuk (tenant_id, external_id) yang sama. Baris terbaru berdasarkan updated_at harus dipertahankan; jika timestamp sama, id terbesar yang menang. Foreign key contact_events.contact_id dapat menunjuk ke salinan mana pun, sehingga menghapus data yang kalah sebelum mengarahkan ulang referensi akan gagal atau menghilangkan relasi tersebut.
customer_contacts(
id bigint primary key,
tenant_id bigint not null,
external_id text not null,
updated_at timestamptz not null,
payload jsonb not null
)
contact_events(
id bigint primary key,
contact_id bigint not null references customer_contacts(id),
event_type text not null,
created_at timestamptz not null
)Rancang perbaikan PostgreSQL yang mempertahankan satu kontak kanonikal deterministik per business key, mempertahankan setiap event, memproses pekerjaan destruktif dalam transaksi terbatas (bounded transactions), dapat diaudit dan dicoba ulang, serta diakhiri dengan keunikan yang ditegakkan oleh basis data. Operasi pembacaan (reads) harus tetap tersedia. Jeda penulisan akhir yang terkendali diperbolehkan, tetapi durasinya harus diukur, bukan diasumsikan.
200 juta baris dan jeda penulisan akhir adalah batasan wawancara. Ini adalah pertanyaan rekayasa data dan SQL yang sulit karena fungsi window hanyalah mekanisme seleksi; jawaban sebenarnya juga harus melindungi integritas referensial, konkurensi, rollback, kesehatan penyimpanan, dan penulisan di masa mendatang. Materi wawancara SQL publik saat ini terus menggunakan deduplikasi ROW_NUMBER dan penyelesaian tie-breaker deterministik sebagai pola wawancara eksplisit. Tidak ada atribusi perusahaan yang dapat diverifikasi, sehingga companyName bernilai null.
Hal yang Dievaluasi oleh Pewawancara
Sinyal pertama adalah apakah kandidat mendefinisikan "duplikat" dan "terbaru" sebelum menulis DELETE. Business key adalah (tenant_id, external_id), sedangkan id mengidentifikasi baris fisik. Urutan total updated_at DESC, id DESC menjadikan tepat satu baris sebagai pemenang bahkan ketika timestamp bernilai sama. RANK dapat mempertahankan beberapa baris yang seri; ROW_NUMBER menetapkan tepat satu posisi 1.
Sinyal kedua adalah kontrol atas batasan destruktif. Jawaban tingkat produksi akan mempratinjau jumlah dan sampel data, membekukan pemetaan loser-to-winner yang tidak dapat diubah (immutable), mempertahankan baris yang kalah atau snapshot yang dapat dipulihkan, dan menghapus berdasarkan primary key. Menghitung ulang peringkat secara independen selama setiap batch dapat mengubah target kumpulan data saat penulisan terus berlanjut dan membuat proses eksekusi sulit dijelaskan.
Sinyal ketiga adalah kebenaran referensial dan transaksional. Setiap referensi anak harus dipindahkan dari yang kalah ke pemenang sebelum induknya dihapus. Aturan keunikan tabel anak dapat membuat pembaruan tersebut bertabrakan (collide), sehingga "perbarui semua foreign key" tidak lengkap tanpa inventarisasi dan kebijakan konflik. Setiap batch harus bersifat idempoten, singkat, dapat diamati, dan aman untuk dihentikan.
Sinyal terakhir adalah apakah pembersihan mengatasi akar penyebabnya. Query yang menghapus duplikat hari ini membiarkan pengimpor besok bebas membuatnya kembali. Invarian yang tahan lama berada dalam unique constraint atau unique index, dengan kontrak null dan normalisasi yang eksplisit. Kandidat juga harus membedakan bukti kebenaran dari bukti performa: nol kunci duplikat dan nol referensi yatim (orphan references) membuktikan semantik; EXPLAIN, lock waits, laju WAL, lag replikasi, dead tuples, dan perilaku autovacuum menentukan tingkat operasi yang dapat diterima.
Pertanyaan untuk Diklarifikasi Sebelum Menjawab
- Kolom mana yang mendefinisikan entitas bisnis? Di sini adalah pasangan persis
(tenant_id, external_id). Case folding, pemangkasan spasi, atau normalisasi Unicode akan mendefinisikan kunci yang berbeda dan harus disepakati sebelum perbaikan. - Baris mana yang menang?
updated_atterbesar, laluidterbesar. Timestamp saja bukanlah urutan total jika dua baris bernilai sama. - Bolehkah baris yang kalah berisi data unik? Jika payload harus digabungkan, "pertahankan yang terbaru" tidaklah cukup. Prompt ini memperlakukan payload terbaru sebagai yang otoritatif dan mengarsipkan baris yang kalah untuk ditinjau.
- Tabel mana saja yang mereferensikan
customer_contacts.id? Buat daftar foreign key yang dideklarasikan dan referensi tingkat aplikasi.contact_eventsditampilkan, tetapi eksekusi nyata harus memeriksa katalog dan dokumentasi kepemilikan. - Bisakah pengalihan referensi menciptakan duplikat pada tabel anak? Jika tabel anak memiliki
UNIQUE(contact_id, event_type, created_at), dua event yang setara dapat bertabrakan setelah konvergensi. Putuskan apakah akan menggabungkan, mempertahankan, atau menolaknya sebelum pembaruan. - Bolehkah penulisan berlanjut selama perbaikan? Batch historis dapat berjalan sementara proses penulisan yang dijaga tetap berlanjut, tetapi pemindaian duplikat akhir dan penyerahan keunikan memerlukan penjaga penulisan tingkat basis data yang terverifikasi atau jeda penulisan yang terukur. Konvensi aplikasi yang dilewati oleh beberapa proses penulisan bukanlah jaminan.
- Apa persyaratan rollback-nya? Simpan cadangan (backup) atau baris yang kalah yang diarsipkan beserta pemetaan anak asli hingga validasi dan jendela rollback selesai. Pemetaan loser-to-winner saja tidak dapat merekonstruksi payload yang dibuang.
- Berapa banyak beban yang dapat diterima? Ukuran batch adalah alat kontrol, bukan konstanta. Mulai dari yang kecil dan lakukan pelambatan (throttle) berdasarkan lock waits, p99 penulisan, WAL, lag replikasi, dead tuples, dan progres autovacuum.
- Bagaimana nilai null harus diperlakukan? Kedua kolom kunci tidak bernilai null di sini. Jika null diizinkan dan harus bertabrakan, keunikan PostgreSQL memerlukan
NULLS NOT DISTINCT; semantik unik default mengizinkan banyak nilai null.
Kerangka Jawaban 30 Detik
"Pertama-tama saya akan membekukan aturan bisnis: baris duplikat memiliki (tenant_id, external_id) yang sama, dan pemenangnya adalah updated_at terbesar, kemudian id terbesar. Saya akan mempratinjau hasil pemeringkatan, mengarsipkan baris yang kalah, dan mematerialisasi satu pemetaan run_id yang tidak dapat diubah dari setiap baris yang kalah ke pemenangnya. Dalam batch-batch kecil yang idempoten, saya akan mencatat dan mengarahkan ulang semua referensi anak, memverifikasi tidak ada yang masih menargetkan baris yang kalah, lalu menghapus baris yang kalah berdasarkan primary key. Saya akan merekonsiliasi jumlah data, sampel payload, kunci duplikat, dan referensi yatim setelah setiap fase. Terakhir, di bawah penjaga penulisan terverifikasi atau jeda penulisan terukur, saya akan menjalankan pembersihan delta terakhir dan membangun unique index, lalu memastikan semua operasi penulisan menggunakan kontrak kunci yang sama. Saya akan menyesuaikan laju berdasarkan metrik produksi dan mempertahankan arsip hingga jendela rollback kedaluwarsa."
Kerangka kerja ini menyatakan seleksi, urutan dependensi, kontrol destruktif, konvergensi, dan pencegahan. SQL mengikuti keputusan-keputusan tersebut; SQL tidak menggantikannya.
Jawaban Mendalam Langkah-demi-Langkah
Langkah 1: Tetapkan Invarian, Kepemilikan, dan Batasan Eksekusi
Tulis pascakondisi sebelum menyentuh data:
- tepat satu kontak ada untuk setiap
(tenant_id, external_id); - ID-nya adalah nilai maksimum di bawah pengurutan
(updated_at, id); - setiap baris
contact_eventsyang sudah ada sebelumnya tetap ada dan mereferensikan pemenang tersebut; - tidak ada referensi yang dideklarasikan atau tingkat aplikasi yang menunjuk ke ID yang dihapus;
- percobaan ulang batch yang sudah selesai mengubah nol baris;
- penulisan baru tidak dapat membuat duplikat business key lainnya.
Tetapkan run_id, pemilik, snapshot sumber atau cadangan, waktu mulai, versi kode, kursor batch, dasbor, ambang jeda, dan batas waktu rollback. Catat jumlah baris pra-perbaikan, jumlah business key yang berbeda, jumlah kunci duplikat, jumlah baris yang kalah, dan jumlah event. Pertahankan total kontrol per tenant sehingga total global tidak dapat menyembunyikan kehilangan di tingkat tenant.
Langkah 2: Pratinjau Pemeringkatan Deterministik
Mulai dengan query read-only dan periksa jumlah serta perbedaan payload:
WITH ranked AS (
SELECT
id,
tenant_id,
external_id,
updated_at,
ROW_NUMBER() OVER (
PARTITION BY tenant_id, external_id
ORDER BY updated_at DESC, id DESC
) AS rn
FROM customer_contacts
)
SELECT tenant_id, external_id, COUNT(*) AS loser_count
FROM ranked
WHERE rn > 1
GROUP BY tenant_id, external_id
ORDER BY loser_count DESC, tenant_id, external_id
LIMIT 100;ROW_NUMBER tepat digunakan karena kontrak membutuhkan satu pemenang. RANK atau DENSE_RANK akan memberikan peringkat yang sama untuk nilai pengurutan yang setara kecuali jika id disertakan, dan ketiadaan penentu seri (tie-breaker) yang deterministik akan membuat eksekusi berulang dapat memilih pemenang yang berbeda. DISTINCT hanya menghapus baris output terpilih yang identik; fungsi ini tidak dapat mempertahankan baris lengkap yang diinginkan berdasarkan aturan bisnis.
Langkah 3: Materialisasi Pemetaan Loser-to-Winner yang Tidak Dapat Diubah
Jangan menghitung ulang pemenang secara terpisah untuk pembaruan anak dan penghapusan. Materialisasi keputusan tersebut satu kali di bawah batasan eksekusi yang telah disepakati. Cuplikan kode menggunakan :name untuk menunjukkan parameter bind yang disediakan oleh pengelola perbaikan:
WITH ranked AS (
SELECT
id,
tenant_id,
external_id,
ROW_NUMBER() OVER w AS rn,
FIRST_VALUE(id) OVER w AS winner_id
FROM customer_contacts
WINDOW w AS (
PARTITION BY tenant_id, external_id
ORDER BY updated_at DESC, id DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
)
INSERT INTO contact_dedup_map (
run_id, loser_id, winner_id, tenant_id, external_id
)
SELECT :run_id, id, winner_id, tenant_id, external_id
FROM ranked
WHERE rn > 1;contact_dedup_map harus memiliki PRIMARY KEY(run_id, loser_id), pemeriksaan bahwa yang kalah dan pemenang berbeda, serta indeks pada (run_id, winner_id). Arsipkan baris kontak kalah yang lengkap di bawah run_id yang sama, atau pertahankan jalur pemulihan point-in-time yang telah teruji. Validasi bahwa setiap pemenang dalam pemetaan ada, tidak ada pemenang yang juga muncul sebagai yang kalah dalam eksekusi yang sama, setiap yang kalah dipetakan satu kali, dan ukuran pemetaan sama dengan jumlah baris kalah yang diukur.
Untuk 200 juta baris, pemeringkatan mungkin memerlukan pemindaian dan pengurutan (sort) yang besar. Jalankan EXPLAIN pada data yang menyerupai produksi, konfirmasikan ruang sementara dan dampak beban kerja, serta jadwalkan atau perlambat sesuai kebutuhan. Covering index dapat membantu rencana yang terukur, tetapi pembuatannya sendiri memakan biaya besar dan tidak boleh ditentukan tanpa bukti.
Langkah 4: Arahkan Ulang Referensi Sebelum Menghapus Data Induk
Inventarisasi setiap referensi terlebih dahulu. Untuk contact_events, pertahankan pemetaan asli dalam tabel audit lalu perbarui rentang ID terbatas:
UPDATE contact_events AS e
SET contact_id = m.winner_id
FROM contact_dedup_map AS m
WHERE m.run_id = :run_id
AND e.contact_id = m.loser_id
AND e.id > :after_event_id
AND e.id <= :batch_end_event_id;Percobaan ulang bersifat idempoten: setelah suatu event menunjuk ke pemenang, event tersebut tidak lagi cocok dengan m.loser_id. Lakukan commit pada setiap batch yang terbatas, simpan kursor hanya setelah commit berhasil, dan rekonsiliasi baris yang diperbarui dengan baris audit referensi. Jika unique constraint pada tabel anak mengalami tabrakan, terapkan aturan penggabungan atau penolakan yang telah disepakati sebelumnya; jangan menonaktifkan constraint dan berharap status akhirnya valid.
Sebelum penghapusan induk, query ini harus mengembalikan nol untuk setiap tabel anak:
SELECT COUNT(*) AS remaining_loser_references
FROM contact_events AS e
JOIN contact_dedup_map AS m
ON m.run_id = :run_id
AND m.loser_id = e.contact_id;Langkah 5: Hapus Baris yang Kalah dalam Batch Singkat yang Dapat Diulang (Restartable)
Hapus hanya ID dari pemetaan yang telah dibekukan:
WITH batch AS (
SELECT loser_id
FROM contact_dedup_map
WHERE run_id = :run_id
AND loser_id > :after_loser_id
ORDER BY loser_id
LIMIT 10000
)
DELETE FROM customer_contacts AS c
USING batch AS b
WHERE c.id = b.loser_id
RETURNING c.id, c.tenant_id, c.external_id;10000 adalah nilai awal untuk wawancara, bukan nilai optimum universal. Sesuaikan ukuran batch dan jeda waktu berdasarkan durasi lock yang diukur, p99, WAL, lag replikasi, dead tuples, dan autovacuum. Output RETURNING menjadi bukti penghapusan. Jika proses terhenti (crash) setelah commit tetapi sebelum penyimpanan kursor, menjalankan ulang batch tersebut hanya akan menemukan ID yang sudah dihapus dan tetap aman.
Operasi delete PostgreSQL dalam jumlah besar menciptakan dead tuples dan WAL. Rencanakan VACUUM normal dan pantau pembengkakan (bloat) tabel serta indeks; jangan gunakan opsi default VACUUM FULL, yang menulis ulang tabel dan mengambil lock yang kuat. Operasi pembacaan tetap tersedia, tetapi saturasi sumber daya masih dapat melanggar SLO layanan.
Langkah 6: Konvergensikan Penulisan dan Pasang Perlindungan Permanen
Perbaikan massal saja tidak dapat menyelesaikan target yang terus bergerak. Sebelum konvergensi akhir, terapkan satu kontrak penulisan yang menserialisasi pembuatan untuk business key yang dinormalisasi yang sama dan memperbarui baris kanonikal alih-alih menyisipkan salinan baru. Verifikasi bahwa setiap API, pengimpor, job, dan penulis basis data langsung mengikutinya. Jika bukti tersebut tidak tersedia, jeda penulisan untuk pembersihan delta akhir dan penyerahan indeks.
Setelah pemindaian duplikat terakhir mengembalikan nol, buat unique index di luar blok transaksi:
CREATE UNIQUE INDEX CONCURRENTLY customer_contacts_business_key_uidx
ON customer_contacts (tenant_id, external_id);CONCURRENTLY menjaga agar penulisan tidak diblokir oleh lock pembuatan indeks normal, tetapi proses ini melakukan lebih banyak pekerjaan, tidak dapat berjalan di dalam blok transaksi, dan dapat gagal saat terjadi pelanggaran keunikan serta meninggalkan indeks yang tidak valid. Periksa validitas, diagnosis race condition data atau penulisan, hapus (drop) indeks yang tidak valid sesuai runbook, dan coba lagi; jangan berasumsi bahwa IF NOT EXISTS membuktikan keberhasilan. Setelah valid, lampirkan sebagai unique constraint bernama dalam langkah DDL terkontrol yang singkat jika tata kelola skema memerlukan semantik constraint.
Jika semua penulisan dijeda dan proses pembuatan reguler yang terukur sesuai dengan batas waktu yang diizinkan, unique index non-konkuren akan lebih sederhana dan lebih cepat. Bentuk konkuren adalah trade-off yang tepat ketika penulisan terjaga yang terverifikasi perlu dilanjutkan; SLO dan uji coba yang akan menentukan pilihan di antara keduanya.
Penulis akhir harus menggunakan invarian keunikan basis data dan kebijakan konflik yang eksplisit. Jangan memperlakukan penanganan pelanggaran unik setelah pembersihan sebagai satu-satunya desain idempotensi: putuskan apakah kunci yang sama memperbarui baris kanonikal, menolak payload yang berkonflik, atau masuk ke peninjauan.
Langkah 7: Validasi, Amati, dan Tutup Jendela Rollback
Rekonsiliasi setidaknya pemeriksaan berikut:
- total kontak setelah perbaikan sama dengan kontak sebelum perbaikan dikurangi baris kalah yang diarsipkan;
GROUP BY tenant_id, external_id HAVING COUNT(*) > 1mengembalikan nilai nol;- setiap pemenang dalam pemetaan ada dan setiap baris yang kalah tidak ada;
- jumlah event anak tidak berubah, sisa referensi baris kalah adalah nol, dan referensi yatim adalah nol;
- sampel pemenang cocok dengan aturan
(updated_at DESC, id DESC)dan payload yang diarsipkan; - penyisipan duplikat ditolak atau mengikuti kebijakan pembaruan yang dideklarasikan;
- menjalankan ulang batch pemetaan, pengalihan referensi, dan penghapusan yang telah selesai akan mengubah nol baris.
Pantau laju batch, error, lock waits baris, p50/p95/p99 penulisan, byte WAL, lag replikasi, dead tuples, progres autovacuum, pertumbuhan pemetaan/arsip, sisa referensi baris kalah, sisa kunci duplikat, dan progres pembuatan unique index. Simpan data arsip dan audit referensi selama jendela rollback yang telah diuji. Hanya setelah itu hapus penjaga penulisan sementara dan tabel perbaikan sesuai dengan kebijakan retensi.
Contoh Jawaban Berkualitas Tinggi
"Saya akan mendefinisikan duplikat sebagai (tenant_id, external_id) yang sama dan memilih baris kanonikal berdasarkan updated_at DESC, id DESC; ID yang unik membuat penanganan hasil seri menjadi deterministik. Sebelum penghapusan, saya akan mencatat jumlah data dasar (baseline), memeriksa perbedaan payload, menginventarisasi setiap referensi, membuat cadangan yang dapat dipulihkan, dan membuat pemetaan run_id yang tidak dapat diubah dari setiap ID yang kalah ke pemenangnya menggunakan ROW_NUMBER dan FIRST_VALUE.
Saya akan mengarsipkan baris-baris yang kalah dan pemetaan anak yang asli. Kemudian saya akan mengarahkan ulang contact_events dalam rentang primary key yang pendek. Setiap batch melakukan commit sebelum kursornya bergerak maju, dan percobaan ulang bersifat idempoten karena event yang sudah dipindahkan tidak lagi cocok dengan ID yang kalah. Constraint anak tetap aktif; setiap tabrakan mengikuti aturan penggabungan atau penolakan yang eksplisit. Hanya setelah setiap tabel anak melaporkan nol referensi baris kalah, saya akan menghapus kontak berdasarkan ID dari pemetaan yang dibekukan, menggunakan transaksi terbatas dan RETURNING sebagai bukti.
Laju operasi mengikuti durasi lock, p99 permintaan, WAL, lag replikasi, dead tuples, dan autovacuum daripada angka batch yang tetap. Saya akan memvalidasi konservasi jumlah baris, satu baris per business key, ketiadaan baris kalah, ketiadaan referensi yatim, jumlah event yang tidak berubah, sampel pemenang deterministik, dan percobaan ulang dengan nol perubahan.
Untuk mencegah terjadinya kembali, semua proses penulisan harus mengacu pada business key ternormalisasi yang sama. Di bawah penjaga penulisan yang terverifikasi atau jeda terukur, saya akan melakukan pembersihan delta akhir dan membangun unique index konkuren pada (tenant_id, external_id) di luar transaksi. Saya akan memverifikasi bahwa indeks tersebut valid karena pembuatan konkuren yang gagal dapat meninggalkan indeks yang tidak valid. Jalur penulisan permanen kemudian akan memperbarui, menolak, atau meninjau konflik sesuai dengan kontrak yang dideklarasikan. Arsip tetap disimpan hingga bukti rollback dan jendela pengamatan selesai."
Jawaban ini membuat operasi yang tidak dapat dibatalkan (irreversible) bergantung pada gerbang pemeriksaan eksplisit dan menjaga SQL, foreign key, perilaku penulisan, serta constraint basis data di bawah satu kontrak business key.
Kesalahan Umum
- Menjalankan
DELETEsegera setelah menemukan duplikat → referensi, payload, dan bukti rollback dapat hilang → pratinjau, arsipkan, petakan, arahkan ulang referensi, validasi, lalu hapus. - Melakukan partisi hanya berdasarkan
external_id→ ID eksternal yang sama di tenant yang berbeda akan digabungkan menjadi satu → gunakan business key yang lengkap(tenant_id, external_id). - Mengurutkan hanya berdasarkan
updated_at→ timestamp yang bernilai sama memungkinkan pemenang yang berbeda pada eksekusi lain → tambahkanidyang unik sebagai penentu seri terakhir. - Menggunakan
RANKdenganrn > 1→ baris-baris terbaru yang bernilai sama dapat sama-sama menerima peringkat 1 → gunakanROW_NUMBERdengan urutan total jika hanya satu baris yang harus bertahan. - Menghitung ulang pemenang di setiap batch → perubahan konkuren dapat menggeser target dan merusak auditabilitas → bekukan satu pemetaan loser-to-winner berversi.
- Menghapus induk sebelum mengarahkan ulang anak → foreign key akan menolak penghapusan atau cascade akan menghapus riwayat yang valid → inventarisasi dan migrasikan semua referensi terlebih dahulu.
- Menonaktifkan constraint demi kecepatan → perbaikan dapat menciptakan data yatim tersembunyi atau tabrakan data anak → pertahankan constraint tetap aktif dan definisikan penanganan konflik.
- Menyebut 10.000 sebagai ukuran batch yang benar → perangkat keras dan beban kerja yang menentukan throughput yang aman → mulai dari yang kecil dan sesuaikan berdasarkan metrik SLO, WAL, lag, dan vacuum.
- Berhenti setelah pembersihan → proses penulisan yang salah akan membuat ulang duplikat → konvergensikan proses penulisan dan tegakkan keunikan di PostgreSQL.
- Berasumsi
CREATE UNIQUE INDEX CONCURRENTLYselalu berhasil → race condition atau duplikat dapat meninggalkan indeks yang tidak valid → periksa validitas dan ikuti prosedur pemulihan yang eksplisit. - Menggunakan
VACUUM FULLsebagai pembersihan rutin → perintah ini menulis ulang dan mengunci tabel secara kuat → pantau vacuum biasa dan pembengkakan, lalu jadwalkan penulisan ulang luar biasa secara terpisah.
Pertanyaan Lanjutan dan Tanggapan
Lanjutan 1: Mengapa tidak menghapus dengan satu CTE dan menyelesaikannya dalam satu transaksi?
Untuk tabel kecil yang terisolasi tanpa referensi dan penulisan yang dijeda, CTE DELETE mungkin sudah cukup. Pada 200 juta baris, satu transaksi dapat menahan lock dan versi baris lama, menghasilkan lonjakan WAL yang besar, menunda replikasi, mempersulit proses vacuum, dan menciptakan skenario pemulihan all-or-nothing. Pemetaan yang dibekukan ditambah batch terbatas memberikan progres, auditabilitas, pelambatan (throttling), dan kemampuan pengulangan (restartability). Gunakan query yang lebih sederhana hanya setelah membuktikan asumsi skala dan dependensinya.
Lanjutan 2: Bagaimana jika dua baris memiliki timestamp yang sama tetapi memiliki payload yang berbeda?
Aturan pada prompt memilih id terbesar, sehingga hasilnya deterministik, tetapi determinisme tidak membuktikan bahwa payload tersebut benar secara semantik. Ukur payload yang berkonflik sebelum perbaikan dan arahkan field berisiko tinggi untuk ditinjau atau ke aturan penggabungan spesifik domain. Arsipkan kedua baris tersebut. Jangan mengarang penggabungan field-demi-field setelah proses penghapusan dimulai.
Lanjutan 3: Bagaimana jika external_id bernilai null?
Pertama tentukan apakah null berarti "tidak diketahui dan independen" atau satu nilai duplikat. Unique constraint dan unique index PostgreSQL memperlakukan null sebagai nilai yang berbeda secara default, sehingga beberapa kunci null diizinkan. Jika null harus bertabrakan, gunakan NULLS NOT DISTINCT dan lakukan pemeringkatan dengan semantik yang sama; jika kontak yang tidak diketahui bersifat independen, pertahankan perilaku default atau gunakan aturan keunikan parsial untuk ID yang tidak null. Pembersihan dan constraint harus selaras.
Lanjutan 4: Bisakah perbaikan dilakukan sepenuhnya secara online tanpa jeda penulisan?
Ya, hanya jika setiap proses penulisan terbukti menggunakan aturan serialisasi atau reservasi yang didukung basis data untuk business key sebelum pemindaian akhir. Dengan demikian, pembersihan historis dapat berkonvergensi dan unique index konkuren dapat dibangun. Jika pengimpor lama atau proses penulisan langsung melewati penjaga tersebut, duplikat baru dapat muncul selama pembangunan indeks dan menyebabkannya gagal. Menggunakan shadow table dan cutover terkontrol adalah opsi lain, tetapi ini menambah kompleksitas dual-write, backfill, foreign key, dan rollback, serta harus dijustifikasi oleh SLO downtime.
Lanjutan 5: Bagaimana Anda menemukan setiap foreign key dan referensi tersembunyi?
Lakukan query pada katalog PostgreSQL untuk foreign key yang relasi referensinya adalah customer_contacts, kemudian periksa view, trigger, konsumen CDC, search index, ekspor data, dan skema aplikasi untuk mencari ID kontak yang tersimpan. Foreign key yang dideklarasikan memberikan bukti yang dapat ditegakkan; dokumentasi kepemilikan dan pencarian kode mencakup referensi tingkat aplikasi. Gerbang penghapusan mencantumkan setiap konsumen yang ditemukan beserta pemeriksaan rekonsiliasinya.
Lanjutan 6: Bagaimana jika pengalihan event melanggar unique constraint pada tabel anak?
Jeda sebelum memperbarui. Tabrakan tersebut berarti dua baris anak menjadi sama setelah ID induknya menyatu. Tentukan apakah keduanya merupakan event duplikat, observasi berbeda yang membutuhkan kunci baru, atau konflik data. Materialisasi grup yang bertabrakan, arsipkan, dan terapkan aturan penggabungan atau penolakan deterministik sebelum mengarahkan ulang. Menghapus constraint anak akan mengubah semantik bisnis dan bukanlah sebuah rencana perbaikan.
Lanjutan 7: Bagaimana Anda membuktikan pemenang yang dipilih adalah yang terbaru setelah tabel berubah selama eksekusi yang panjang?
Pemetaan membuktikan pemenang di bawah snapshot atau batasan eksekusi yang dicatat, bukan di bawah penulisan masa depan yang tidak terbatas. Lindungi proses penulisan agar operasi selanjutnya memperbarui baris kanonikal yang dipetakan, catat versi sumber, dan lakukan pemindaian delta akhir sebelum penyerahan keunikan. Jika aturan bisnis mengharuskan pembaruan berikutnya untuk menggantikan payload, perbarui pemenangnya; jangan membuat kontak fisik lainnya. Nyatakan jaminan temporal dalam catatan audit.