Saat Menghapus Kolom Database Mengakibatkan Gangguan Produksi: Mengelola Perubahan Skema yang Destruktif

Anda memiliki migrasi database yang menghapus kolom yang tidak terpakai. SQL-nya terlihat bersih. Migrasi berjalan tanpa error. Namun lima menit kemudian, alert mulai bermunculan. Aplikasi produksi melempar error karena ada kode yang masih mereferensi kolom tersebut. Apa yang tampak seperti pembersihan sederhana justru menyebabkan gangguan.

Skenario ini lebih umum daripada yang diakui sebagian besar tim. Masalahnya bukan pada migrasi itu sendiri. Masalahnya adalah asumsi bahwa menghapus sesuatu dari database itu aman hanya karena Anda pikir tidak ada yang menggunakannya lagi.

Apa yang Membuat Suatu Perubahan Bersifat Destruktif

Perubahan database terbagi dalam dua kategori. Perubahan aditif menambahkan sesuatu yang baru: kolom baru, tabel baru, indeks baru. Ini umumnya aman karena kode yang ada hanya mengabaikan apa yang tidak diketahuinya.

Perubahan destruktif menghapus, mengganti nama, atau mengubah struktur yang sudah ada. Menghapus kolom, mengganti nama tabel, mengubah tipe kolom, atau menghapus constraint semuanya bersifat destruktif. Risikonya jelas: jika aplikasi yang sedang berjalan masih bergantung pada struktur tersebut, aplikasi akan rusak begitu perubahan diterapkan.

Bahayanya diperkuat oleh strategi deployment modern. Rolling update dan blue-green deployment menyebabkan versi aplikasi lama dan baru berjalan berdampingan selama beberapa menit atau jam. Migrasi destruktif yang berjalan selama deployment akan langsung merusak instance lama yang masih melayani traffic.

Pola Migrasi Multi-Fase

Pendekatan paling aman adalah jangan pernah menghapus apa pun dalam satu langkah. Sebaliknya, pecah perubahan destruktif menjadi beberapa fase. Setiap fase harus kompatibel dengan versi aplikasi yang berjalan pada saat itu.

Diagram alir berikut mengilustrasikan tiga fase penggantian nama kolom yang aman, menunjukkan versi aplikasi mana yang kompatibel di setiap langkah.

flowchart TD A[Fase 1: Tambah kolom baru, pertahankan lama] --> B[Deploy app v1: baca dari lama, tulis ke keduanya] B --> C[Fase 2: Backfill data, tulis ganda] C --> D[Deploy app v2: baca dari baru, tulis ke keduanya] D --> E[Fase 3: Hentikan penulisan ke kolom lama] E --> F[Deploy app v3: baca dan tulis hanya kolom baru] F --> G[Fase 4: Hapus kolom lama] G --> H[Deploy app v4: hanya gunakan kolom baru] style A fill:#d4edda,stroke:#28a745 style C fill:#fff3cd,stroke:#ffc107 style E fill:#f8d7da,stroke:#dc3545 style G fill:#f8d7da,stroke:#dc3545

Pertimbangkan mengganti nama kolom dari status menjadi status_code. Satu migrasi yang mengganti nama kolom akan merusak kode apa pun yang masih membaca status. Pendekatan multi-fase terlihat seperti ini:

Fase 1: Tambahkan kolom baru tanpa menghapus yang lama. Salin data dari kolom lama ke kolom baru. Perbarui kode aplikasi untuk membaca dari kolom baru sambil tetap menulis ke keduanya. Deploy perubahan ini.

Fase 2: Setelah memastikan semua instance aplikasi menggunakan kolom baru, hentikan penulisan ke kolom lama. Perbarui kode untuk hanya mereferensi status_code. Deploy lagi.

Fase 3: Setelah Anda yakin tidak ada kode yang berjalan menyentuh kolom lama, hapus kolom tersebut dalam migrasi terpisah. Jadwalkan ini selama jam traffic rendah.

Pola yang sama berlaku untuk menghapus tabel. Buat view atau tabel baru yang menggantikan fungsionalitas lama. Arahkan kode aplikasi ke struktur baru. Tunggu hingga tidak ada kode yang mereferensi tabel lama. Kemudian hapus.

Soft Delete sebagai Jaring Pengaman

Terkadang Anda ingin menghapus data dari tampilan aplikasi tanpa benar-benar menghapusnya dari database. Di sinilah soft delete membantu.

Alih-alih menjalankan pernyataan DELETE, tambahkan kolom seperti deleted_at atau is_active. Aplikasi memfilter baris yang dihapus dengan klausa WHERE. Data tetap berada di tabel untuk audit, pemulihan, atau dependensi tak terduga dari fitur lain.

Soft delete sangat berguna ketika Anda tidak sepenuhnya yakin apakah data masih diperlukan. Ini memberi Anda buffer keamanan. Jika ada yang rusak, Anda dapat mengembalikan visibilitas tanpa perlu restore database. Konsekuensinya adalah tabel Anda tumbuh lebih besar dan kueri perlu memperhitungkan filter. Namun bagi banyak tim, konsekuensi ini sebanding dengan keamanan yang didapat.

Menangani Constraint dengan Hati-hati

Menghapus constraint seperti foreign key atau unique constraint memang tidak berisiko sebesar menghapus data, tetapi tetap memiliki konsekuensi. Constraint menegakkan integritas data. Jika kode aplikasi Anda bergantung pada database untuk mencegah duplikasi entri atau record yatim, menghapus constraint dapat menyebabkan korupsi data.

Sebelum menghapus constraint, audit codebase untuk memastikan tidak ada logika yang bergantung padanya. Jika database Anda mendukungnya, pertimbangkan untuk menonaktifkan constraint terlebih dahulu daripada menghapusnya. Ini memungkinkan Anda menguji dampaknya tanpa kehilangan kemampuan untuk mengaktifkannya kembali secara permanen.

Daftar Periksa Praktis untuk Perubahan Destruktif

  • Verifikasi tidak ada kode aplikasi yang berjalan mereferensi struktur yang akan Anda hapus. Periksa rilis saat ini dan deployment yang sedang berlangsung.
  • Pecah perubahan menjadi setidaknya dua migrasi: satu untuk menambahkan struktur baru dan mengarahkan kode, satu lagi untuk menghapus struktur lama setelah periode tunggu.
  • Jalankan migrasi destruktif secara terpisah dari deployment fitur. Jangan menggabungkan penghapusan kolom dengan rilis endpoint baru.
  • Jadwalkan perubahan destruktif selama jendela traffic rendah. Bahkan dengan perencanaan multi-fase, masalah tak terduga lebih mudah ditangani ketika lebih sedikit pengguna yang terpengaruh.
  • Setelah menghapus struktur lama, bersihkan sisa-sisa seperti kolom yang diganti nama atau constraint yang dinonaktifkan dalam migrasi lanjutan. Namun ingat: pembersihan juga bersifat destruktif, jadi terapkan pendekatan multi-fase yang sama.

Prinsip Inti

Jangan pernah menghapus sesuatu yang mungkin masih diakses oleh aplikasi yang berjalan. Ini terdengar jelas, tetapi ini adalah kesalahan paling umum yang dilakukan tim dengan migrasi database. Tekanan untuk menjaga skema tetap bersih, asumsi bahwa "tidak ada yang menggunakan itu lagi," dan keinginan untuk mengirim satu migrasi bersih daripada beberapa migrasi kecil semuanya mendorong tim menuju penghapusan satu langkah yang berisiko.

Migrasi multi-fase membutuhkan lebih banyak waktu dan lebih banyak deployment. Namun mereka mencegah jenis gangguan produksi yang mengubah pembersihan skema sederhana menjadi rollback darurat. Skema yang bersih tidak sebanding dengan aplikasi yang rusak.