Migrasi database di Rails sebenarnya cuma soal menjalankan rails db:migrate. Yang bikin rumit adalah momen ketika migrasi itu berjalan di server produksi yang sedang dipakai pengguna. Ada tabel besar, ada query yang jalan bersamaan, dan ada downtime yang harus dihindari. Sembilan tips berikut membahas urutan kerja yang aman untuk memindahkan skema tanpa membuat aplikasi mati.
1. Backup dulu, jangan pernah skip
Sebelum menyentuh apa pun, ambil dump database production. Di PostgreSQL, pg_dump dengan opsi -Fc sudah cukup untuk menghasilkan file yang bisa direstore kapan saja. Simpan juga catatan versi migrasi terakhir yang berjalan, karena angka itu yang akan kamu pakai kalau harus balik posisi.
Kalau ada migrasi yang salah tipe kolom dan datanya terlanjur terpotong, file dump inilah satu-satunya jalan pulang. Untuk aplikasi yang datanya berubah tiap menit, jadwalkan backup tepat sebelum jendela migrasi dibuka.
2. Uji migrasi di salinan database yang ukurannya mirip production
Menjalankan migrasi di database lokal yang isinya sepuluh baris tidak membuktikan apa-apa. Buat salinan database production, restore ke server staging, lalu jalankan migrasi di sana. Kamu akan melihat berapa lama prosesnya, apakah ada lock yang menggantung, dan apakah query aplikasi masih jalan normal setelah skema berubah.
Cara ini juga mengungkap masalah yang tidak terlihat di lokal, misalnya index yang tidak terpakai atau kolom yang ternyata menyimpan nilai null padahal akan dijadikan NOT NULL.
3. Pisahkan perubahan skema dari perubahan data
Migrasi yang menambah kolom sekaligus mengisi datanya lewat model ActiveRecord rawan gagal. Model di aplikasi bisa berubah kapan saja, sedangkan file migrasi harus tetap bisa dijalankan bertahun-tahun kemudian. Kalau butuh mengisi data, tulis SQL langsung di dalam migrasi atau pakai class kecil yang didefinisikan di file itu sendiri, bukan model dari app/models.
Polanya sederhana: satu migrasi untuk add_column, migrasi terpisah untuk backfill data. Dengan begitu kamu bisa menjalankan yang pertama, memeriksa hasilnya, baru lanjut ke yang kedua.
4. Tambah kolom baru dengan default yang aman
Menambah kolom dengan default dan null: false pada tabel besar bisa memicu penulisan ulang seluruh tabel di PostgreSQL versi lama. Kalau versi database kamu sudah modern, operasi ini biasanya cepat. Kalau belum, tambahkan kolom tanpa default dulu, isi nilainya bertahap, baru pasang constraint setelah semua baris terisi.
Perhatikan juga tabel yang dipakai untuk membaca data penting. Menahan lock terlalu lama di tabel seperti users atau orders akan membuat request pengguna menunggu.
5. Hindari rename kolom langsung di production
Ganti nama kolom terdengar sepele, tapi kode lama yang masih berjalan akan langsung error begitu nama lamanya hilang. Strategi yang lebih aman: tambah kolom baru, tulis data ke dua kolom sekaligus lewat kode aplikasi, deploy, pastikan tidak ada lagi yang membaca kolom lama, baru hapus kolom lama di migrasi berikutnya.
Proses ini butuh dua sampai tiga kali deploy. Memang lebih lama, tetapi pengguna tidak melihat error sama sekali. Kalau aplikasimu punya banyak pembaca database seperti laporan atau job background, pola ini menyelamatkan kamu dari kejaran bug yang sulit dilacak.
6. Buat index setelah data selesai diisi
Menambah index pada tabel besar mengunci tabel selama proses pembuatan berjalan. PostgreSQL menyediakan CREATE INDEX CONCURRENTLY yang tidak mengunci penulisan. Di Rails, kamu bisa memakai opsi algorithm: :concurrently pada add_index, dengan catatan migrasi tersebut tidak boleh dibungkus transaksi.
Letakkan pembuatan index di migrasi terpisah dari pembuatan kolom. Kalau kolom baru belum dipakai query apa pun, tunggu sampai datanya terisi dan pola query-nya jelas sebelum memasang index.
7. Siapkan rollback yang benar-benar bisa jalan
Method down di migrasi sering ditulis asal-asalan karena dianggap tidak akan dipakai. Begitu migrasi bermasalah di production, justru method itulah yang dieksekusi. Tulis down untuk setiap perubahan yang kamu buat, lalu uji rails db:rollback di staging.
Untuk migrasi yang menghapus data, rollback tidak akan mengembalikan isi tabel. Di kasus seperti ini, backup dari langkah pertama adalah satu-satunya penyelamat. Tulis catatan singkat di file migrasi bahwa rollback butuh restore manual.
8. Pantau query dan lock selama migrasi berjalan
Jalankan migrasi sambil memantau log database dan response time aplikasi. Di PostgreSQL, query ke pg_stat_activity dan pg_locks menunjukkan sesi mana yang sedang menunggu lock. Kalau ada transaksi panjang yang menahan tabel, migrasi bisa menggantung tanpa pesan error yang jelas.
Untuk aplikasi Rails, perhatikan juga job background yang mungkin sedang memproses data di tabel yang sama. Hentikan sementara worker yang tidak perlu, atau atur jadwal migrasi di luar jam sibuk. Detail soal mengelola koneksi dan query di production bisa kamu baca di tips mengelola database di Ruby on Rails.
9. Dokumentasikan urutan deploy dan rollback
Migrasi yang melibatkan dua kali deploy butuh catatan urutan yang jelas: deploy kode apa dulu, kapan migrasi dijalankan, kapan kolom lama dihapus. Tulis di pull request atau dokumen internal supaya orang lain yang mengambil alih bisa mengikuti tanpa menebak.
Kalau tim kamu bekerja dengan developer dari luar, pastikan dokumen ini ikut masuk ke serah terima. Kebiasaan mendokumentasikan langkah deploy juga berguna saat menyiapkan rilis besar, seperti yang dibahas di langkah deploy aplikasi Rails ke server produksi.
Ringkasan
Migrasi database yang aman bertumpu pada backup yang siap dipakai, pengujian di salinan database yang realistis, dan perubahan bertahap yang bisa dibatalkan. Jangan buru-buru menggabungkan semua perubahan ke dalam satu file migrasi besar. Memecahnya jadi beberapa langkah kecil membuat setiap kesalahan lebih mudah dilacak dan diperbaiki.
Kalau aplikasimu juga punya komponen frontend yang mengambil data lewat AJAX, cara membuat live search dengan JavaScript dan PHP di panduan live search bisa jadi referensi tambahan saat menyusun query yang efisien.
Pertanyaan yang Sering Diajukan
Apakah rails db:migrate aman dijalankan saat aplikasi sedang melayani request?
Tergantung isi migrasinya. Menambah tabel atau kolom tanpa constraint berat biasanya aman. Operasi yang mengunci tabel besar, seperti menambah index tanpa CONCURRENTLY atau mengubah tipe kolom, sebaiknya dijalankan di jendela waktu yang sudah disiapkan. Uji dulu di staging dengan ukuran data mirip production untuk tahu berapa lama prosesnya.
Bagaimana kalau migrasi sudah terlanjur gagal di tengah jalan?
Periksa dulu apakah database masih dalam kondisi setengah jadi. Beberapa database mendukung transaksi DDL sehingga migrasi yang gagal otomatis dibatalkan. Kalau tidak, kamu perlu memperbaiki manual berdasarkan backup atau menjalankan rails db:rollback jika versi migrasinya tercatat. Untuk error yang muncul saat aplikasi berjalan, langkah di panduan debugging Rails bisa membantu melacaknya.
Perlukah menulis migrasi manual kalau perubahan skemanya kecil?
Tetap tulis migrasinya. Perubahan kecil yang dilakukan langsung lewat klien database akan membuat skema di mesin lain berbeda, dan itu sumber bug yang sulit dilacak. Dengan file migrasi, siapa pun bisa membangun ulang struktur database dari nol dan riwayat perubahannya tetap tercatat.
