Mengapa Rollback Database Tidak Sesederhana Rollback Aplikasi

Saat versi baru aplikasi Anda bermasalah, perbaikannya biasanya mudah. Anda cukup menekan tombol rollback di pipeline deployment, dan versi lama akan kembali berjalan. Server berhenti menjalankan kode yang rusak dan mulai menjalankan kode sebelumnya. Kecuali Anda mengubah dependensi sistem atau konfigurasi, aplikasi akan kembali normal dalam hitungan menit.

Ini berhasil karena aplikasi pada dasarnya bersifat stateless. Aplikasi bisa berhenti, di-restart, atau berganti versi tanpa kehilangan sesuatu yang permanen. Data yang diproses berasal dari pengguna atau dari database. Jika aplikasi mati beberapa detik, pengguna mungkin menyadarinya, tetapi tidak ada data yang hilang. Begitu versi lama berjalan lagi, semuanya berlanjut seolah tidak terjadi apa-apa.

Database tidak bekerja seperti itu.

Realitas Stateful pada Database

Database adalah komponen stateful. Database menyimpan data yang harus bertahan di berbagai versi aplikasi. Profil pengguna, transaksi, catatan pesanan, pengaturan konfigurasi—semuanya hidup di dalam database dan harus tetap utuh. Saat Anda mengubah skema database—menambahkan kolom, mengubah tipe data, atau menghapus tabel—database mencatat perubahan itu secara permanen. Data yang sudah tersimpan tidak otomatis kembali ke bentuk sebelumnya hanya karena Anda mengembalikan aplikasi ke versi lama.

Inilah perbedaan mendasar yang sering menjebak banyak tim. Mereka mengira rollback database bekerja seperti rollback aplikasi: jalankan perintah kebalikan, dan semuanya kembali seperti sebelum migrasi. Kenyataannya, membatalkan perubahan skema jauh lebih kompleks dan berisiko.

Contoh Konkret Masalahnya

Bayangkan tim Anda menambahkan kolom phone_number ke tabel users melalui migrasi database. Migrasi berjalan sukses, dan dalam beberapa jam berikutnya, pengguna mulai mengisi nomor telepon mereka. Kemudian seseorang menemukan bug pada kode aplikasi yang membaca kolom tersebut. Tim memutuskan untuk rollback aplikasi ke versi sebelumnya. Aplikasi pun pulih, dan bug menghilang.

Diagram urutan berikut mengilustrasikan perbedaan antara rollback aplikasi dan rollback database dalam skenario yang sama:

Perhatikan migrasi SQL yang memperkenalkan kolom ini:

-- Up migration: add phone_number column
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20) NOT NULL DEFAULT '';

-- Down migration: remove phone_number column
ALTER TABLE users DROP COLUMN phone_number;

Setelah migrasi up berjalan dan pengguna memasukkan nomor telepon mereka, menjalankan migrasi down akan menghapus semua nilai tersebut. Data itu hilang—bukan hanya tersembunyi, tetapi benar-benar dihapus dari tabel.

sequenceDiagram participant AppV2 as App v2 participant DB as Database participant AppV1 as App v1 Note over AppV2,AppV1: Rollback Aplikasi AppV2->>DB: Gunakan kolom phone_number AppV2->>AppV2: Error terdeteksi AppV2->>AppV1: Rollback ke v1 AppV1->>DB: Berfungsi (tidak pakai phone_number) Note over AppV1: Berhasil Note over AppV2,AppV1: Rollback Database AppV2->>DB: Tambah kolom phone_number AppV2->>AppV2: Error terdeteksi AppV2->>DB: Jalankan migrasi down DB->>DB: Hapus kolom & data AppV1->>DB: Coba baca skema lama DB-->>AppV1: Ketidakcocokan kode-skema Note over AppV1: Masih rusak

Namun, kolom phone_number masih ada di database. Data yang dimasukkan pengguna masih ada. Jika Anda sekarang menjalankan migrasi rollback untuk menghapus kolom tersebut, semua nomor telepon itu akan hilang. Permanen. Dan data itu mungkin sudah digunakan oleh fitur lain atau dilihat oleh pengguna lain. Anda tidak bisa begitu saja menghapusnya tanpa konsekuensi.

Risiko kehilangan data inilah mengapa rollback database tidak bisa dianggap enteng. Setiap kali migrasi mengubah skema, data mungkin diubah, dipindahkan, atau dihapus. Mengembalikan skema lama berarti mengembalikan bentuk data lama. Tanpa mekanisme yang menjamin data dapat dikembalikan ke kondisi persis sebelumnya, rollback skema dapat menyebabkan data tidak konsisten atau hilang.

Masalah Ketidakcocokan Kode-Skema

Selain kehilangan data, ada masalah kedua: ketidakcocokan antara kode aplikasi dan skema database.

Saat Anda rollback aplikasi ke versi lama, kode lama itu mungkin tidak mengenali kolom atau tabel baru yang ada di database. Kode tersebut bisa crash saat mencoba membaca kolom yang tidak diharapkan, atau gagal menyisipkan data karena constraint yang tidak diketahuinya.

Sebaliknya, jika Anda menjalankan migrasi rollback yang menghapus kolom yang masih digunakan oleh kode aplikasi saat ini, aplikasi akan langsung rusak. Karena deployment aplikasi dan migrasi database jarang dieksekusi pada waktu yang persis sama, Anda tidak bisa menjamin kedua sisi tetap sinkron selama rollback.

Ketidakcocokan ini sangat berbahaya di production. Bahkan beberapa detik inkonsistensi dapat menyebabkan error, transaksi gagal, atau data korup yang sulit dipulihkan.

Mengapa Strategi Rollback yang Sama Tidak Berlaku

Rollback aplikasi berhasil karena versi lama adalah kondisi baik yang sudah diketahui. Kodenya sama seperti sebelum deployment. Lingkungannya sama. Satu-satunya yang berubah adalah binary mana yang berjalan.

Rollback database tidak memiliki "kondisi baik yang diketahui" dengan cara yang sama. Skema dan data sudah bergerak maju. Menjalankan migrasi kebalikan tidak memberi Anda database yang persis sama seperti sebelumnya. Ini memberi Anda skema yang terlihat seperti yang lama, tetapi dengan data yang mungkin telah diubah, ditambahkan, atau dihapus dengan cara yang tidak dapat dibalik tanpa perencanaan yang cermat.

Beberapa tim mencoba mengakali ini dengan mengambil backup database penuh sebelum setiap migrasi. Pendekatan itu memiliki masalah tersendiri:

  • Mengembalikan backup membutuhkan waktu—seringkali menit atau jam, bukan detik.
  • Data apa pun yang ditulis setelah backup diambil akan hilang.
  • Layanan lain yang bergantung pada database yang sama mungkin rusak selama proses restore.
  • Restore itu sendiri bisa gagal, membuat Anda berada dalam kondisi yang lebih buruk.

Jalur yang Lebih Aman: Roll Forward

Karena risiko-risiko ini, banyak tim berpengalaman menganggap rollback database sebagai opsi terakhir. Alih-alih mencoba membatalkan migrasi yang gagal, mereka lebih memilih roll forward: menulis migrasi baru yang memperbaiki masalah tanpa membalikkan skema.

Misalnya, jika sebuah migrasi secara tidak sengaja menghapus kolom yang masih diperlukan, pendekatan roll forward adalah menambahkan kolom tersebut kembali dalam migrasi baru, daripada mencoba membatalkan penghapusan. Ini menjaga skema bergerak dalam satu arah dan menghindari kompleksitas pembalikan transformasi data.

Roll forward tidak selalu memungkinkan. Terkadang kerusakannya terlalu parah, atau perbaikannya memerlukan perubahan skema yang tidak bisa diekspresikan sebagai migrasi maju. Namun dalam banyak kasus, ini lebih aman daripada mencoba rollback yang bisa kehilangan data atau merusak aplikasi.

Daftar Periksa Praktis untuk Keamanan Perubahan Database

Sebelum menjalankan migrasi database apa pun di production, lakukan daftar periksa ini:

  • Apakah migrasi dapat dibalik tanpa kehilangan data? Jika tidak, rencanakan strategi roll forward.
  • Apakah ada backup database yang diambil tepat sebelum migrasi? Uji bahwa backup tersebut benar-benar bisa di-restore.
  • Apakah migrasi kompatibel dengan kode aplikasi saat ini? Kode lama harus tetap berfungsi saat migrasi berjalan.
  • Apakah ada layanan atau database lain yang bergantung pada skema yang Anda ubah? Koordinasikan dengan tim mereka.
  • Apakah Anda memiliki cara untuk mendeteksi bahwa migrasi menyebabkan masalah sebelum pengguna melaporkannya? Monitoring dan alerting harus sudah siap.

Kesimpulan

Rollback database bukanlah tombol undo yang sederhana. Ini adalah operasi berisiko tinggi yang dapat menyebabkan kehilangan data, error aplikasi, dan downtime berkepanjangan. Perlakukan dengan hati-hati seperti Anda menangani operasi bedah: rencanakan ke depan, siapkan backup, dan utamakan perbaikan maju (forward fix) bila memungkinkan. Rollback yang paling aman adalah yang tidak pernah perlu Anda jalankan.