Saat Skema Database Anda Benar, Tapi Datanya Salah

Anda baru saja menjalankan migrasi database yang menambahkan kolom baru. Semua terlihat baik. Perubahan skema berhasil, kolomnya ada, dan aplikasi berjalan. Tapi kemudian seseorang menyadari: semua pengguna yang mendaftar tiga tahun lalu seharusnya sudah ditandai sebagai terverifikasi, tapi ternyata tidak. Atau nomor telepon yang Anda migrasikan kini memiliki format yang tidak konsisten karena data lama tidak mengikuti aturan baru.

Skemanya benar. Tipe kolomnya sesuai. Konstrainnya valid. Masalahnya ada pada data itu sendiri.

Ini adalah situasi yang terasa lebih buruk daripada migrasi yang gagal. Migrasi yang gagal itu jelas. Anda melihat errornya, Anda tahu ada yang rusak, dan Anda bisa bertindak. Tapi migrasi yang berhasil dengan data buruk itu samar. Ia bisa berada di production selama berjam-jam atau berhari-hari sebelum ada yang menyadarinya. Dan saat Anda menyadarinya, insting alaminya adalah panik dan mencari cara untuk mengembalikan semuanya.

Tapi mengembalikan skema untuk memperbaiki data justru menciptakan masalah baru. Aplikasi mungkin tidak bisa bekerja dengan skema lama lagi. Anda mungkin kehilangan data yang sudah benar. Dan Anda membatalkan perubahan struktural yang sebenarnya tepat, hanya untuk memperbaiki konten yang salah.

Masalah Sebenarnya Bukan pada Skema

Saat sebuah migrasi menambahkan kolom seperti is_verified dengan nilai default false, perubahan skemanya sederhana. Kolomnya ada, default-nya berfungsi, dan record baru akan berperilaku benar. Masalahnya adalah pengguna lama yang seharusnya terverifikasi kini ditandai sebagai belum terverifikasi. Skema tidak menyebabkan ini. Logika migrasi tidak menyebabkan ini. Celahnya ada pada pemahaman tentang bagaimana seharusnya data yang sudah ada.

Contoh umum lainnya: sebuah migrasi mengubah penyimpanan nomor telepon untuk mewajibkan kode negara. Format barunya benar, dan entri baru akan mengikuti aturan. Tapi nomor telepon lama yang disimpan tanpa kode negara kini menjadi tidak konsisten. Skemanya baik-baik saja. Datanya tidak.

Dalam kedua kasus, solusinya bukan mengembalikan skema. Solusinya adalah memperbaiki data sambil mempertahankan skema.

Compensating Script: Perbaiki Data Tanpa Menyentuh Struktur

Compensating script adalah migrasi yang hanya mengubah data, bukan skema. Ia berjalan sebagai migrasi biasa, melalui pipeline yang sama, dan mengikuti proses deployment yang sama. Tapi alih-alih ALTER TABLE, CREATE INDEX, atau ADD COLUMN, ia hanya berisi pernyataan UPDATE, INSERT, atau DELETE yang mengoreksi data.

Tujuannya sederhana: membawa data ke kondisi yang benar tanpa mengubah struktur tabel.

Berikut contoh praktisnya. Setelah menambahkan kolom currency dengan nilai default 'IDR', tim menyadari bahwa semua transaksi dari mitra internasional harus menggunakan 'USD'. Compensating script-nya akan seperti ini:

UPDATE transactions SET currency = 'USD' WHERE partner_type = 'international';

Tidak ada perubahan skema. Tidak ada kolom baru. Tidak ada konversi tipe. Hanya koreksi data yang terarah.

Compensating script juga menangani kegagalan migrasi parsial. Bayangkan sebuah migrasi yang membuat tabel baru dan memindahkan data dari tabel lama. Beberapa baris gagal ditransfer karena pelanggaran konstrain. Alih-alih mengembalikan seluruh migrasi dan memulai dari awal, compensating script dapat menangani baris yang tersisa. Ia menyisipkan atau memperbarui hanya record yang terlewat, tanpa menjalankan ulang migrasi penuh.

Buat Compensating Script Anda Idempoten

Ada satu aturan yang lebih penting dari yang lain: compensating script harus idempoten. Menjalankan script dua kali harus menghasilkan hasil yang sama seperti menjalankannya sekali.

Ini bukan kekhawatiran teoretis. Dalam praktiknya, migrasi sering dijalankan ulang. Pipeline restart. Environment disegarkan. Seseorang menjalankan migrasi secara manual saat debugging. Jika script Anda tidak idempoten, menjalankannya dua kali dapat merusak data Anda.

Perbaikannya sederhana. Selalu periksa kondisi saat ini sebelum melakukan perubahan. Gunakan klausa WHERE yang cukup spesifik untuk hanya memengaruhi baris yang perlu dikoreksi. Jika database Anda mendukungnya, gunakan klausa ON CONFLICT untuk penyisipan.

Alih-alih ini:

UPDATE transactions SET currency = 'USD' WHERE partner_type = 'international';

Tulis ini:

UPDATE transactions SET currency = 'USD' WHERE partner_type = 'international' AND currency IS DISTINCT FROM 'USD';

Perbedaannya kecil tapi krusial. Versi kedua hanya memperbarui baris yang currency-nya belum 'USD'. Menjalankannya seratus kali hanya akan memengaruhi baris yang perlu diubah, dan hanya pada eksekusi pertama.

Kapan Compensating Script Tidak Cukup

Compensating script bukan solusi universal. Ia bekerja saat skema benar dan hanya data yang perlu diperbaiki. Jika skema itu sendiri salah, Anda tetap memerlukan migrasi skema yang tepat.

Misalnya, jika Anda menambahkan kolom dengan tipe data yang salah, atau jika konstrain kolom terlalu ketat untuk data yang perlu disimpan, compensating script tidak dapat membantu. Anda perlu mengubah skema. Demikian pula, jika migrasi memperkenalkan bug yang merusak data dengan cara yang tidak dapat diperbaiki dengan pernyataan UPDATE sederhana, Anda mungkin memerlukan pendekatan yang lebih kompleks.

Tapi untuk kasus umum di mana skema benar dan data salah, compensating script lebih aman daripada alternatif lainnya. Ia menghindari risiko down migration, tidak memerlukan restore dari backup, dan dapat berjalan saat aplikasi masih melayani traffic.

Daftar Periksa Singkat untuk Menulis Compensating Script

  • Pastikan skema sudah benar sebelum menulis script. Jika struktur perlu diubah, tangani itu terlebih dahulu.
  • Tulis script sebagai migrasi baru, bukan sebagai hotfix yang diterapkan langsung ke database.
  • Buat setiap pernyataan idempoten. Periksa kondisi sebelum memperbarui atau menyisipkan.
  • Uji script terhadap salinan data production, bukan hanya terhadap database kosong.
  • Sertakan logging atau komentar yang menjelaskan mengapa koreksi data diperlukan, sehingga anggota tim di masa depan memahami konteksnya.

Intisari

Saat migrasi salah, instingnya sering kali mengembalikan semuanya. Tapi mengembalikan skema adalah operasi berat yang dapat merusak aplikasi dan kehilangan data yang benar. Compensating script memberi Anda alat yang lebih ringan dan lebih presisi. Ia memungkinkan Anda memperbaiki data sambil mempertahankan skema yang berfungsi. Lain kali Anda melihat migrasi yang berhasil tapi meninggalkan data buruk, tanyakan pada diri sendiri: apakah skemanya salah, atau datanya yang salah? Jika jawabannya data, tulislah compensating script. Itu lebih cepat, lebih aman, dan tidak terlalu mengganggu daripada rollback.