Menulis Skrip Migrasi Database yang Tidak Akan Merusak Produksi
Fitur baru Anda sudah siap. Kode sudah di-review, diuji, dan di-merge. Tapi ada satu hal yang menghalangi Anda dan deployment: perubahan database. Mungkin Anda perlu menambahkan kolom, mengganti nama tabel, atau membuat indeks baru. Pertanyaannya bukan apakah perubahan itu berfungsi di laptop Anda. Pertanyaannya adalah apakah perubahan itu akan berfungsi di produksi tanpa merusak apa pun.
Di sinilah skrip migrasi berperan. Skrip ini bukan sekadar file SQL. Skrip ini adalah cara disiplin untuk mengubah skema database Anda tanpa tebak-tebakan, tanpa langkah manual, dan tanpa perasaan cemas saat deployment gagal.
Pola Dasar: Satu File, Satu Perubahan
Ide utamanya sederhana. Setiap perubahan database berada di file-nya sendiri. File tersebut memiliki pengenal unik, biasanya timestamp atau nomor urut, dan berisi SQL yang diperlukan untuk menerapkan perubahan. Anda juga membuat file rollback yang sesuai untuk membatalkannya.
Misalnya Anda perlu menambahkan kolom phone ke tabel users. Alih-alih login ke produksi dan menjalankan ALTER TABLE secara langsung, Anda membuat file bernama 20241101_add_phone_to_users.sql. Di dalamnya, Anda tulis:
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
Kemudian Anda buat 20241101_add_phone_to_users_rollback.sql:
ALTER TABLE users DROP COLUMN phone;
Kedua file masuk ke repositori Anda bersama kode aplikasi. Keduanya melalui proses code review. Keduanya di-merge seperti perubahan lainnya.
Kenapa harus file terpisah? Karena setiap perubahan memiliki risikonya sendiri. Saat Anda memisahkan perubahan ke dalam file individual, Anda bisa menerapkan satu perubahan, mengamati dampaknya, lalu melanjutkan ke perubahan berikutnya. Jika semuanya digabung dalam satu skrip raksasa, Anda tidak bisa tahu bagian mana yang menyebabkan kegagalan. Lebih parah lagi, jika migrasi gagal di tengah jalan, Anda tidak tahu di mana ia berhenti.
Urutan Itu Lebih Penting dari yang Anda Kira
Timestamp atau nomor urut bukan sekadar konvensi penamaan. Ini menentukan urutan eksekusi. Pipeline migrasi Anda membaca semua file, mengurutkannya berdasarkan pengenal ini, dan menjalankannya dari yang terlama hingga terbaru. Ini menjamin bahwa setiap lingkungan - development, staging, produksi - menerapkan perubahan dalam urutan yang sama.
Tidak ada lagi "berfungsi di mesin saya tapi gagal di server" karena urutan migrasinya berbeda. Tidak ada lagi inkonsistensi diam-diam di mana satu lingkungan melewatkan satu langkah.
Buat Skrip Anda Idempoten
Idempoten adalah kata keren untuk ide sederhana: menjalankan skrip yang sama dua kali harus menghasilkan hasil yang sama dan tidak menyebabkan error.
Bandingkan dua pernyataan ini:
-- Tidak idempoten: akan error jika kolom sudah ada
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Idempoten: aman dijalankan berkali-kali
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
Versi kedua lebih aman. Pipeline terkadang perlu menjalankan ulang migrasi dari awal, misalnya saat membuat database staging baru. Jika skrip Anda tidak idempoten, operasi sederhana itu berubah menjadi sesi debugging.
Idempotensi juga membantu selama pengembangan. Developer sering menerapkan migrasi, memeriksa hasilnya, dan ingin memulai dari awal. Dengan skrip idempoten, mereka bisa menjalankan ulang tanpa harus menghapus dan membuat ulang seluruh database.
Rollback Bukan Opsional
Setiap migrasi maju harus memiliki rollback yang sesuai. Ini bukan hanya untuk keadaan darurat produksi. Developer terus-menerus menggunakan rollback selama pengembangan lokal untuk menguji perubahan dan melakukan iterasi. Tanpa skrip rollback, mereka harus menghapus seluruh database dan menjalankan semua migrasi dari awal, yang lambat dan membuat frustrasi.
Tapi inilah kebenaran jujurnya: rollback tidak selalu bisa mengembalikan data. Jika migrasi menghapus kolom, rollback bisa membuat ulang kolom itu, tapi datanya sudah hilang. Jika migrasi mengganti nama tabel, rollback bisa mengembalikan namanya, tapi semua penulisan yang terjadi di antaranya akan hilang.
Ini bukan berarti rollback tidak berguna. Ini berarti Anda perlu memahami apa yang sebenarnya dipulihkan oleh rollback Anda. Terkadang rollback hanya memulihkan struktur skema, bukan data. Itu tetap berharga. Ini membawa Anda kembali ke kondisi yang diketahui di mana Anda bisa memulihkan atau menerapkan ulang perubahan.
Untuk perubahan destruktif - menghapus kolom, menghilangkan tabel, mengubah tipe data - Anda memerlukan strategi terpisah. Kami akan membahasnya nanti. Untuk saat ini, aturannya sederhana: setiap migrasi harus memiliki rollback, meskipun hanya memulihkan struktur.
Bagaimana Pekerjaan Paralel Tetap Aman
Beberapa developer yang mengerjakan fitur berbeda sering membutuhkan perubahan database. Satu menambahkan kolom untuk fitur A. Yang lain membuat tabel untuk fitur B. Keduanya membuat file migrasi dengan timestamp berbeda. Ketika kedua branch di-merge, pipeline mengurutkan file berdasarkan timestamp dan menerapkannya secara berurutan.
Konflik hanya terjadi ketika dua perubahan menyentuh objek skema yang sama. Itu adalah konflik nyata yang memerlukan penyelesaian manual, seperti konflik kode. Pola file migrasi tidak menghilangkan ini, tapi membuat konflik menjadi terlihat dan eksplisit.
Tabel Pelacakan
Bagaimana pipeline Anda tahu migrasi mana yang sudah dijalankan? Pipeline menggunakan tabel khusus di dalam database itu sendiri. Tabel ini mencatat setiap migrasi yang telah diterapkan, beserta timestamp atau checksum. Saat pipeline berjalan, ia memeriksa tabel ini, membandingkannya dengan daftar file migrasi, dan hanya menerapkan yang belum ada.
Mekanisme ini sudah terpasang di sebagian besar alat migrasi, tapi memahaminya membantu Anda melakukan debug saat terjadi masalah. Jika migrasi diterapkan sebagian, atau jika seseorang menjalankan skrip secara manual di luar pipeline, tabel pelacakan akan memberi tahu Anda.
Daftar Periksa Praktis untuk Menulis Skrip Migrasi
Sebelum Anda melakukan merge pada file migrasi, jalankan daftar singkat ini:
- Apakah skrip memiliki pengenal unik (timestamp atau urutan)?
- Apakah ada skrip rollback yang sesuai?
- Apakah skrip idempoten? Bisakah dijalankan dua kali tanpa error?
- Apakah rollback benar-benar mengembalikan kondisi sebelumnya?
- Sudahkah Anda menguji migrasi maju dan rollback pada salinan data produksi?
- Apakah migrasi mengunci tabel? Jika ya, bisakah dijalankan saat lalu lintas rendah?
Kesimpulan
Migrasi database bukan sekadar SQL. Ini adalah kontrak antara tim Anda dan data produksi Anda. Setiap file migrasi mewakili sebuah keputusan: apa yang berubah, dalam urutan apa, dan bagaimana cara membatalkannya jika terjadi kesalahan. Perlakukan setiap file dengan perhatian yang sama seperti kode aplikasi Anda. Review. Uji. Pastikan memiliki rollback. Database produksi Anda akan berterima kasih.