Mengapa Perubahan Skema Sekecil Apa Pun Bisa Merusak Database Produksi Anda
Anda memiliki aplikasi yang berjalan di produksi. Aplikasi tersebut melayani ribuan pengguna setiap menit. Suatu pagi, Anda memutuskan untuk menambahkan satu kolom ke tabel database. Hanya satu kolom. Perubahannya tampak tidak berbahaya di atas kertas. Namun beberapa saat setelah migrasi dimulai, pengguna mulai melihat error. Permintaan mengalami timeout. Pendaftaran baru gagal. Tim Anda bergegas melakukan rollback.
Skenario ini terjadi lebih sering dari yang diperkirakan sebagian besar engineer. Perubahan skema yang tampak sepele di laptop developer bisa melumpuhkan sistem produksi. Memahami mengapa hal ini terjadi sangat penting bagi siapa pun yang men-deploy perubahan database bersamaan dengan kode aplikasi.
Perbedaan Mendasar Antara Kode dan Skema
Saat Anda mengubah kode aplikasi, efeknya relatif terbatas. Versi baru menggantikan versi lama. Jika terjadi kesalahan, Anda dapat men-deploy versi sebelumnya dan mengembalikan operasi normal. Risikonya nyata, tetapi jalur pemulihannya jelas.
Perubahan skema database tidak bekerja seperti itu. Saat Anda mengubah struktur tabel, Anda memodifikasi fondasi yang menjadi sandaran setiap instance aplikasi yang berjalan. Tidak ada "tukar" bersih antara skema lama dan baru. Skema lama hilang begitu migrasi selesai. Jika terjadi kerusakan, rollback perubahan skema bisa lebih kompleks dan berisiko daripada perubahan aslinya.
Asimetri ini adalah akar penyebab banyak insiden produksi yang terlacak kembali ke modifikasi database yang tampaknya sepele.
Penambahan Kolom Kecil yang Menyebabkan Masalah Besar
Pertimbangkan contoh konkret. Anda memiliki tabel users dengan kolom email yang didefinisikan sebagai varchar(255). Anda memutuskan untuk meningkatkan batas menjadi varchar(500). Ini hanya perubahan tipe satu kolom. Seberapa burukkah itu?
Selama migrasi, database mungkin perlu mengunci tabel untuk merestrukturisasi kolom. Selama kunci itu ditahan, tidak ada aplikasi yang dapat membaca atau menulis ke tabel users. Jika aplikasi Anda menangani ratusan permintaan per detik, bahkan beberapa detik kunci tabel dapat menyebabkan kaskade timeout dan permintaan gagal. Pengguna mengalami error. Monitoring alert berbunyi. Tim panik.
Sekarang pertimbangkan menambahkan kolom baru phone_number ke tabel yang sama. Migrasi menambahkan kolom dengan constraint NOT NULL dan tanpa nilai default. Instance aplikasi yang menjalankan kode lama tidak tahu kolom ini ada. Saat mereka mengeksekusi pernyataan INSERT yang menghilangkan kolom baru, database menolak query. Tiba-tiba, pendaftaran pengguna baru berhenti bekerja di semua instance yang masih menjalankan kode lama. Perubahannya hanya menambah satu kolom. Dampaknya adalah pemadaman total pendaftaran.
Berikut adalah SQL yang akan menyebabkan pemadaman yang dijelaskan di atas:
-- Berisiko: mengunci seluruh tabel users, memblokir semua baca dan tulis
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20) NOT NULL;
-- Alternatif yang lebih aman: tambahkan kolom tanpa NOT NULL terlebih dahulu,
-- lalu backfill, lalu tambahkan constraint dengan lock timeout
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);
-- Backfill dalam batch (kode aplikasi menangani nilai yang hilang)
UPDATE users SET phone_number = 'unknown' WHERE phone_number IS NULL;
-- Tambahkan NOT NULL dengan lock timeout untuk menghindari pemblokiran tak terbatas
SET lock_timeout = '5s';
ALTER TABLE users ALTER COLUMN phone_number SET NOT NULL;
Pernyataan pertama mengunci tabel selama seluruh durasi operasi. Pada tabel besar, ini bisa memakan waktu menit, menyebabkan timeout kaskade di semua instance aplikasi.
Perubahan Tipe yang Merusak Query Secara Diam-diam
Beberapa perubahan skema tampak aman tetapi mengubah perilaku query dengan cara yang halus. Mengubah kolom primary key dari INT ke BIGINT adalah contoh umum. Aplikasi mendekati batas integer, jadi perubahan itu diperlukan. Namun selama proses konversi, query yang mengandalkan indeks untuk kolom tersebut mungkin menjadi lambat atau berhenti menggunakan indeks sama sekali. Database mungkin perlu menulis ulang seluruh tabel dan semua indeksnya. Untuk tabel besar, ini bisa memakan waktu menit atau jam.
Bahkan setelah konversi selesai, kode aplikasi mungkin memiliki asumsi tentang tipe data. Kode yang memformat ID untuk ditampilkan, meneruskannya ke API eksternal, atau menggunakannya dalam operasi aritmatika bisa rusak secara diam-diam. Perubahan skema itu benar, tetapi asumsi yang tertanam dalam kode aplikasi tidak.
Menghapus Kolom yang Tidak Digunakan Juga Berisiko
Menghapus kolom yang tampaknya tidak digunakan di aplikasi utama sepertinya pembersihan yang aman. Namun database jarang hanya memiliki satu konsumen. Batch job yang berjalan setiap malam mungkin membaca kolom itu untuk pelaporan. Layanan lawas yang tidak diingat siapa pun mungkin melakukan query padanya. Tim data science mungkin memiliki skrip yang menariknya untuk analisis.
Saat Anda menghapus kolom, semua konsumen itu langsung rusak. Laporan malam gagal. Layanan lawas mulai melempar error. Pipeline data science berhenti menghasilkan hasil. Apa yang tampak seperti operasi pembersihan berubah menjadi insiden multi-tim.
Mengapa Perubahan Skema Adalah Breaking Changes
Dalam kode aplikasi, breaking change biasanya jelas: Anda menghapus fungsi, mengubah signature method, atau mengubah format respons API. Di database, breaking changes lebih sulit dideteksi karena database adalah sumber daya bersama dengan banyak konsumen tak terlihat.
Satu tabel database mungkin diakses oleh:
- Aplikasi utama
- Prosesor job latar belakang
- Alat pelaporan
- Pipeline analitik data
- Layanan lawas
- Query ad-hoc dari tim operasi
- Integrasi pihak ketiga
Setiap konsumen memiliki asumsinya sendiri tentang skema. Perubahan yang aman untuk aplikasi utama mungkin merusak skrip pelaporan yang berjalan sebulan sekali. Karena skrip itu jarang berjalan, kerusakan mungkin tidak terdeteksi selama berminggu-minggu.
Prinsip Inti
Tidak ada yang namanya perubahan skema yang benar-benar kecil. Setiap modifikasi pada struktur database adalah operasi terkoordinasi yang memerlukan perencanaan, pengujian, dan eksekusi yang hati-hati. Ukuran perubahan dalam hal baris kode migrasi tidak berkorelasi dengan besarnya potensi dampak.
Daftar Periksa Praktis Sebelum Perubahan Skema Apa Pun
Sebelum Anda menjalankan migrasi di produksi, verifikasi poin-poin ini:
- Apakah Anda tahu setiap aplikasi, layanan, dan skrip yang mengakses tabel ini?
- Bisakah Anda menjalankan migrasi tanpa mengunci tabel untuk penulisan?
- Apakah perubahan itu merusak query yang ada atau asumsi aplikasi?
- Bisakah kode aplikasi lama dan baru hidup berdampingan dengan skema baru?
- Apakah Anda memiliki rencana rollback yang teruji dan tidak memerlukan kehilangan data?
- Sudahkah Anda memeriksa transaksi berjalan lama yang mungkin bertentangan dengan migrasi?
- Apakah ada dashboard monitoring yang akan menunjukkan error query segera setelah migrasi?
Apa Artinya Ini untuk Proses Deployment Anda
Perubahan skema database memerlukan strategi deployment yang berbeda dari perubahan kode aplikasi. Perubahan tersebut harus dapat dibalik, kompatibel mundur jika memungkinkan, dan diuji terhadap volume data yang realistis. Perubahan juga perlu dikoordinasikan dengan semua tim yang bergantung pada database.
Perlakukan setiap perubahan skema sebagai operasi berisiko tinggi, tidak peduli seberapa kecil tampaknya. Kolom yang Anda tambahkan hari ini dapat menyebabkan pemadaman besok. Tipe yang Anda ubah dapat merusak laporan minggu depan. Tabel yang Anda hapus mungkin adalah tabel yang menjadi sandaran skrip rekan kerja.
Rencanakan deployment database Anda dengan perhatian yang sama seperti yang Anda berikan pada perubahan infrastruktur kritis. Karena itulah sebenarnya mereka.