Mengubah Skema Database Tanpa Menghentikan Produksi

Anda memiliki database yang telah berjalan selama lima, sepuluh, atau lima belas tahun. Database itu menyimpan jutaan transaksi, ribuan tabel, dan ratusan stored procedure yang ditulis oleh orang-orang yang mungkin sudah tidak bekerja di perusahaan. Setiap kali seseorang perlu menambahkan kolom, mengubah tipe data, atau memperbaiki indeks, pertanyaan yang sama muncul: "Jika migrasi ini gagal, berapa lama waktu pemulihan yang dibutuhkan?"

Di organisasi seperti ini, database bukan sekadar tempat menyimpan data. Ia adalah jantung operasional bisnis. Jika aplikasi mati, pengguna bisa menunggu. Jika database rusak, data bisa hilang secara permanen. Itulah mengapa perubahan skema dan data sering dianggap sebagai aktivitas berisiko tinggi, dijadwalkan pada Sabtu malam pukul 2 pagi, dengan harapan tidak ada yang menyadari sampai Senin pagi.

Pendekatan ini tidak berskala. Semakin cepat tim ingin mengirimkan fitur, semakin sering perubahan database dibutuhkan. Jika setiap perubahan harus menunggu jendela pemeliharaan mingguan, tim produk akan frustrasi. Namun jika perubahan dilakukan secara sembarangan, risiko kerusakan data menjadi nyata.

Pertanyaan sebenarnya bukanlah "alat migrasi mana yang terbaik." Melainkan: "Bagaimana cara mengubah skema database tanpa menghentikan layanan, dan bagaimana cara kembali jika terjadi kesalahan?"

Migrasi Aman Dimulai dengan Langkah Kecil

Prinsip intinya sederhana: setiap perubahan harus memungkinkan tanpa memutus koneksi ke aplikasi yang berjalan, dan harus dapat dibalikkan tanpa kehilangan data. Ini berarti perubahan skema perlu dilakukan dalam beberapa langkah kecil, bukan satu lompatan besar.

Ambil contoh menambahkan kolom baru. Di database lawas, Anda menambahkan kolom dengan nilai default atau membuatnya nullable. Anda tidak menambahkan batasan ketat langsung. Instance aplikasi lama tetap berjalan karena mereka tidak membaca kolom baru. Instance aplikasi baru mulai menulis ke kolom tersebut. Setelah semua instance diperbarui dan berjalan stabil, Anda menambahkan batasan seperti NOT NULL atau foreign key dalam migrasi terpisah. Jika terjadi kesalahan di tengah jalan, rollback semudah mengabaikan kolom baru. Tidak perlu menghapus tabel atau mengembalikan dari backup.

Diagram urutan berikut mengilustrasikan proses langkah demi langkah yang aman seperti dijelaskan di atas:

sequenceDiagram participant OldApp as Aplikasi (versi lama) participant DB as Database participant NewApp as Aplikasi (versi baru) Note over DB: Langkah 1: Tambah kolom nullable DB->>DB: ALTER TABLE ADD COLUMN nullable Note over OldApp,DB: Langkah 2: Aplikasi lama tetap berjalan tanpa terpengaruh OldApp->>DB: Baca/tulis (abaikan kolom baru) DB-->>OldApp: Respons Note over DB,NewApp: Langkah 3: Deploy aplikasi baru yang menggunakan kolom NewApp->>DB: Tulis ke kolom baru DB-->>NewApp: OK NewApp->>DB: Baca dari kolom baru DB-->>NewApp: Data Note over DB: Langkah 4: Tambah batasan DB->>DB: ALTER TABLE ADD NOT NULL Note over OldApp: Langkah 5: Hapus aplikasi lama OldApp-->>OldApp: Dinonaktifkan

Pola yang sama berlaku untuk mengubah tipe data. Misalkan kolom price saat ini bertipe INTEGER tetapi perlu menjadi DECIMAL. Pendekatan aman: tambahkan kolom baru bernama price_decimal, isi dengan nilai yang dikonversi dari kolom lama, biarkan aplikasi membaca dari kolom baru sambil tetap menulis ke keduanya, lalu hapus kolom lama setelah semuanya stabil. Rollback berarti aplikasi membaca dari kolom lama lagi, yang masih ada.

Contoh SQL berikut menunjukkan skrip maju dan rollback untuk menambahkan kolom dengan aman:

-- Migrasi maju 1: tambah kolom sebagai nullable
ALTER TABLE products ADD COLUMN discount_rate DECIMAL(5,2) NULL;

-- Isi data mundur (jalankan setelah aplikasi menulis ke kolom baru)
UPDATE products SET discount_rate = 0.00 WHERE discount_rate IS NULL;

-- Migrasi maju 2: tambah batasan NOT NULL
ALTER TABLE products ALTER COLUMN discount_rate SET NOT NULL;

-- Skrip rollback (membalikkan kedua langkah)
ALTER TABLE products ALTER COLUMN discount_rate DROP NOT NULL;
ALTER TABLE products DROP COLUMN discount_rate;

Perubahan Kompleks Membutuhkan Eksekusi Paralel

Untuk perubahan yang lebih kompleks seperti memisahkan satu tabel menjadi dua atau menggabungkan beberapa tabel, teknik yang digunakan disebut eksekusi paralel. Aplikasi menulis ke struktur lama dan baru secara bersamaan, sementara kueri baca dialihkan secara bertahap. Tim dapat membandingkan hasil dari kedua struktur untuk memastikan tidak ada perbedaan data. Jika ada anomali muncul, aplikasi dapat beralih kembali ke struktur lama tanpa kehilangan data.

Pendekatan ini membutuhkan pengkodean yang cermat di sisi aplikasi. Aplikasi perlu menyadari kedua struktur dan menangani penulisan ke keduanya. Aplikasi juga membutuhkan logika untuk memutuskan struktur mana yang akan dibaca. Ini tidak sepele, tetapi jauh lebih aman daripada mencoba migrasi big-bang tunggal yang entah berhasil sempurna atau menyebabkan insiden besar.

Migrasi Tentang Data, Bukan Hanya Skema

Kesalahan umum adalah memperlakukan migrasi database sebagai operasi skema murni. Data yang sudah ada harus tetap konsisten setelah migrasi. Setiap migrasi membutuhkan dua skrip: skrip maju dan skrip rollback. Skrip rollback bukan sekadar kebalikan dari skrip maju. Ia harus mengembalikan data ke keadaan yang persis sama seperti sebelum migrasi, termasuk data apa pun yang mungkin telah dimodifikasi oleh aplikasi selama proses migrasi.

Misalnya, jika migrasi mengganti nama kolom dan mentransformasi nilainya, skrip rollback harus membalikkan nama kolom dan transformasi nilai. Jika aplikasi telah menulis data baru ke kolom yang diganti namanya selama jendela migrasi, skrip rollback harus menangani data itu dengan benar, bukan sekadar membuangnya.

Tempat Migrasi dalam Pipeline

Dalam pipeline CI/CD, migrasi database harus menjadi tahap terpisah yang berjalan independen dari deployment aplikasi. Pipeline tidak boleh menjalankan migrasi pada saat yang sama dengan kode baru di-deploy. Sebaliknya, migrasi dijalankan terlebih dahulu. Setelah migrasi dikonfirmasi berhasil, versi aplikasi baru di-deploy. Jika migrasi gagal, pipeline berhenti dan tim mendapat notifikasi sebelum aplikasi terpengaruh.

Pemisahan ini sangat penting. Jika migrasi dan deployment terjadi bersamaan dan terjadi kesalahan, sulit untuk mengetahui apakah masalah berasal dari perubahan skema atau kode baru. Menjalankannya secara berurutan memberikan kepemilikan yang jelas atas setiap kegagalan.

Kapan Memerlukan Persetujuan Manual

Organisasi yang telah beroperasi lama biasanya mengadopsi aturan sederhana: migrasi yang mengubah data (bukan hanya skema) memerlukan persetujuan manual. Migrasi skema saja yang bersifat aditif, seperti menambahkan kolom nullable, dapat berjalan otomatis. Ini bukan karena otomatisasi tidak dapat dipercaya. Ini karena perubahan data memiliki konsekuensi yang lebih sulit diprediksi daripada perubahan struktural.

Kolom nullable baru tidak akan merusak apa pun. Tetapi migrasi yang memperbarui jutaan baris, mentransformasi nilai, atau menggabungkan tabel dapat memperkenalkan bug halus yang hanya muncul dalam kondisi data tertentu. Tinjauan manusia sebelum migrasi semacam itu adalah jaring pengaman, bukan hambatan.

Daftar Periksa Praktis untuk Migrasi Database yang Aman

  • Tambahkan kolom sebagai nullable atau dengan default terlebih dahulu, lalu tambahkan batasan kemudian.
  • Ubah tipe data dengan menambahkan kolom baru, mengisinya, dan mengalihkan pembacaan secara bertahap.
  • Untuk restrukturisasi kompleks, jalankan struktur lama dan baru secara paralel dan bandingkan hasilnya.
  • Selalu tulis skrip rollback yang mengembalikan data ke keadaan persis sebelum migrasi.
  • Jalankan migrasi sebelum deployment aplikasi, bukan pada waktu yang sama.
  • Wajibkan persetujuan manual untuk migrasi yang mengubah data, izinkan migrasi skema aditif berjalan otomatis.

Kesimpulan

Migrasi database tidak harus menakutkan. Kuncinya adalah memecah setiap perubahan menjadi langkah-langkah kecil yang dapat dibalikkan. Tambahkan sebelum Anda menghapus. Jalankan yang lama dan yang baru berdampingan sebelum beralih. Dan selalu miliki jalan kembali yang tidak bergantung pada pemulihan dari backup. Ketika Anda memperlakukan setiap migrasi sebagai serangkaian langkah aman yang dapat diuji, Anda menghilangkan rasa takut dan menjadikan perubahan database sebagai bagian normal dari proses pengiriman Anda.