Mengapa Rollback Database Lebih Sulit daripada Rollback Aplikasi

Anda men-deploy versi baru aplikasi. Terjadi kesalahan. Anda menekan tombol rollback, versi lama berjalan kembali, dan dalam hitungan menit semuanya kembali normal. Tidak ada data yang hilang, tidak ada efek samping yang tersisa. Prosesnya terasa bersih dan reversibel.

Sekarang bayangkan skenario berbeda. Anda menjalankan migrasi database yang menambahkan kolom status ke tabel orders. Migrasi selesai, kolom baru diisi dengan nilai default, dan aplikasi yang diperbarui mulai menulis data nyata ke kolom tersebut. Beberapa jam kemudian, Anda menemukan bug pada logika aplikasi yang membuat nilai status tidak dapat diandalkan. Anda memutuskan untuk rollback aplikasi ke versi sebelumnya. Kode lama kini berjalan kembali. Tapi kolom status masih ada. Data yang ditulis ke dalamnya masih ada. Dan kode aplikasi lama Anda mungkin tidak tahu cara menangani kolom tambahan itu, atau lebih buruk lagi, bisa rusak karena menemukan kolom yang tidak pernah diharapkan.

Ini adalah masalah intinya: rollback aplikasi hanya memulihkan kode. Rollback database harus memulihkan struktur dan data ke kondisi sebelum migrasi. Dan itu tidak terjadi secara otomatis.

Mengapa Rollback Aplikasi Sederhana

Saat Anda rollback aplikasi, pada dasarnya Anda menukar satu set kode yang dapat dieksekusi dengan yang lain. Versi lama mengambil alih, mulai menangani permintaan baru, dan sistem berjalan seperti biasa. Tidak ada status persisten yang dimodifikasi selama rollback itu sendiri. Database tetap persis seperti sebelum rollback dipicu. Satu-satunya yang berubah adalah versi kode yang berjalan.

Kesederhanaan inilah yang membuat banyak tim menganggap rollback sebagai jaring pengaman. Jika ada yang salah, cukup kembalikan dan coba lagi nanti. Ini bekerja dengan baik untuk layanan stateless dan aplikasi di mana skema database tidak berubah antar versi.

Mengapa Rollback Database Berbeda

Rollback database melibatkan pembatalan perubahan struktural pada penyimpanan data yang sedang berjalan. Itu berarti menghapus kolom, mengembalikan tabel yang dihapus, atau mengembalikan constraint yang diubah. Dan tidak seperti kode aplikasi, database berisi data yang mungkin telah dimodifikasi, ditambahkan, atau dihapus sejak migrasi dijalankan.

Pertimbangkan migrasi yang menghapus kolom bernama legacy_flag dari tabel users. Jika Anda perlu rollback, Anda harus menambahkan kolom itu kembali. Tapi bagaimana dengan data yang ada di kolom tersebut? Jika migrasi hanya menghapusnya, data itu hilang kecuali Anda mencadangkannya sebelumnya. Jika migrasi mengganti nama atau mentransformasi kolom, Anda perlu membalikkan transformasi itu dengan tepat, tanpa merusak data baru yang mungkin telah ditulis sementara itu.

Berikut contoh konkret yang mengilustrasikan masalahnya. Migrasi maju menambahkan kolom, dan rollback yang sesuai mencoba menghapusnya:

-- Forward migration: tambah kolom NOT NULL dengan default
ALTER TABLE orders ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending';

-- Beberapa jam kemudian, kode aplikasi baru menulis nilai status nyata
-- Beberapa baris kini memiliki status = 'shipped', 'cancelled', dll.

-- Rollback migration: hapus kolom
ALTER TABLE orders DROP COLUMN status;
-- Ini berhasil, tapi semua data status hilang secara permanen.

Jika rollback migration malah mencoba menyimpan data dengan mengganti nama atau mentransformasi kolom, ia harus menangani constraint, indeks, dan data baru yang ditulis oleh aplikasi lama setelah rollback — sebuah proses yang rapuh dan seringkali tidak teruji.

Ini bukan masalah teoretis. Tim yang mengandalkan down migration — skrip yang membalikkan perubahan yang dibuat oleh forward migration — sering menemukan bahwa skrip tersebut jarang diuji, kadang rusak, dan hampir selalu berisiko dijalankan di produksi. Down migration yang gagal di tengah jalan dapat meninggalkan database dalam keadaan tidak konsisten, dengan beberapa perubahan dikembalikan dan yang lainnya tidak.

Pendekatan yang Lebih Aman: Migrasi Backward-Compatible

Strategi yang lebih andal adalah merancang setiap migrasi database agar backward-compatible. Artinya, perubahan skema yang Anda buat tidak boleh merusak versi lama aplikasi Anda. Jika Anda perlu menambahkan kolom baru, tambahkan tanpa menghapus atau mengubah kolom yang sudah ada. Aplikasi lama tetap berfungsi karena ia mengabaikan kolom baru. Aplikasi baru mulai menggunakannya. Jika versi baru ternyata bermasalah, Anda bisa rollback aplikasi tanpa menyentuh database sama sekali. Kolom tambahan tetap ada, tapi kode lama tidak peduli.

Pendekatan ini membutuhkan disiplin. Setiap perubahan skema harus dievaluasi dampaknya terhadap semua versi aplikasi yang mungkin masih berjalan. Tapi ini jauh lebih aman daripada mengandalkan down migration yang bisa gagal atau kehilangan data.

Berikut cara kerja migrasi backward-compatible untuk operasi umum:

  • Menambahkan kolom: Cukup tambahkan. Jangan buat NOT NULL kecuali Anda bisa memberikan nilai default yang berfungsi untuk kode lama dan baru. Aplikasi lama tidak akan membaca atau menulis ke kolom tersebut, jadi tidak akan terpengaruh.

  • Mengganti nama kolom: Jangan mengganti nama secara langsung. Sebagai gantinya, tambahkan kolom baru dengan nama baru, perbarui aplikasi untuk menulis ke kedua kolom selama periode transisi, lalu hapus kolom lama di migrasi terpisah setelah memastikan kode lama tidak lagi berjalan.

  • Menghapus kolom: Hentikan penggunaannya di aplikasi terlebih dahulu. Deploy perubahan itu. Kemudian, di migrasi terpisah, hapus kolom tersebut. Jika Anda perlu rollback aplikasi, kolom masih ada.

  • Mengubah tipe kolom: Tambahkan kolom baru dengan tipe baru, migrasikan data secara bertahap, perbarui aplikasi untuk menggunakan kolom baru, dan baru kemudian hapus kolom lama.

Setiap pola ini menambah langkah, tapi setiap langkah dapat dibalikkan tanpa kehilangan data.

Biaya Nyata Down Migration

Beberapa tim masih lebih suka down migration karena tampak lebih sederhana untuk ditulis. Satu skrip yang membalikkan perubahan terasa lebih bersih daripada pendekatan backward-compatible multi-langkah. Tapi biaya kesederhanaan itu muncul saat tekanan datang.

Ketika insiden produksi terjadi dan Anda perlu rollback dengan cepat, hal terakhir yang Anda inginkan adalah menjalankan down migration yang tidak teruji yang mungkin gagal, memakan waktu terlalu lama, atau diam-diam menghapus data. Tekanan waktu, stres, dan kurangnya fallback yang bersih membuat down migration menjadi perjudian.

Migrasi backward-compatible menghilangkan perjudian itu. Mereka memungkinkan Anda rollback aplikasi secara independen dari database. Mereka memberi Anda waktu untuk memutuskan apa yang harus dilakukan dengan perubahan skema tanpa memaksa pembalikan yang berisiko dan segera.

Checklist Praktis untuk Perencanaan Rollback Database

Jika Anda ingin menghindari skenario rollback yang menyakitkan, berikut checklist singkat untuk ditinjau sebelum setiap migrasi:

  • Dapatkah versi aplikasi lama tetap berjalan dengan benar setelah migrasi ini?
  • Jika migrasi menambahkan kolom, apakah kode lama mengabaikannya?
  • Jika migrasi menghapus kolom, apakah kode lama sudah berhenti menggunakannya?
  • Jika migrasi mengganti nama atau mengubah kolom, apakah ada periode transisi di mana struktur lama dan baru hidup berdampingan?
  • Apakah ada cara yang teruji dan aman untuk membalikkan migrasi ini tanpa kehilangan data?

Jika Anda tidak bisa menjawab ya untuk semua pertanyaan di atas, migrasi Anda membawa risiko rollback yang belum Anda atasi.

Kesimpulan

Rollback database bukan hanya tentang mengembalikan versi. Ini tentang menjaga data tetap utuh dan konsisten sambil memastikan aplikasi dapat kembali ke keadaan sebelumnya tanpa efek samping. Jalur teraman adalah merancang migrasi yang tidak memaksa Anda memilih antara rollback aplikasi dan kehilangan data. Bangun backward compatibility ke dalam setiap perubahan skema, dan perlakukan down migration sebagai opsi terakhir, bukan strategi default. Diri Anda di masa depan, yang sedang debug insiden jam 2 pagi, akan berterima kasih.