Menambah satu kolom pada tabel pengguna terdengar seperti pekerjaan lima menit. Masalahnya muncul ketika tabel itu berisi jutaan baris, sedang dipakai banyak pengunjung, atau dibaca oleh beberapa versi aplikasi sekaligus. Perintah ALTER TABLE yang tampak sepele dapat menunggu metadata lock, membangun ulang tabel, atau membuat kode aplikasi lama gagal membaca struktur baru.
Karena itu, migrasi database sebaiknya diperlakukan sebagai bagian dari deployment, bukan pekerjaan manual yang dijalankan setelah kode selesai. Prinsip yang paling berguna adalah expand and contract: perluas struktur database lebih dulu, pindahkan aplikasi secara bertahap, lalu hapus bagian lama setelah benar-benar tidak dipakai.
Kenapa perubahan schema bisa berisiko?
Database bukan hanya tempat menyimpan data. Ia juga sedang melayani query dari aplikasi, proses latar belakang, laporan, dan mungkin integrasi API. Saat struktur tabel diubah, database dapat membutuhkan lock atau menyalin ulang data, tergantung jenis operasi dan versi MySQL.
MySQL mendukung beberapa bentuk online DDL pada tabel InnoDB. Sebagian operasi dapat berjalan tanpa menghentikan perubahan data, sedangkan operasi lain tetap membutuhkan pembangunan ulang tabel atau lock yang lebih berat. Dokumentasi MySQL juga menekankan bahwa dukungan tersebut bergantung pada jenis perubahan, engine, versi server, dan pilihan seperti ALGORITHM serta LOCK. Rujuk tabel dukungan online DDL di dokumentasi MySQL sebelum menjalankan migrasi di production.
Risiko lainnya adalah asumsi bahwa semua perubahan dapat dibatalkan dengan transaksi. Pada MySQL, banyak perintah DDL seperti ALTER TABLE, CREATE TABLE, dan DROP TABLE dapat menyebabkan implicit commit. Artinya, ROLLBACK tidak selalu mengembalikan keadaan sebelum perubahan schema. Daftar perintah yang memicu implicit commit perlu dipahami sebelum mencampur DDL dengan perubahan data.
Pola expand and contract dalam praktik
1. Expand: tambahkan struktur yang kompatibel
Misalkan aplikasi lama menyimpan nama lengkap pada kolom name, tetapi versi baru ingin memisahkan data menjadi first_name dan last_name. Jangan langsung menghapus name atau menjadikannya tidak boleh kosong. Tambahkan kolom baru terlebih dahulu, dengan aturan yang masih bisa diterima oleh aplikasi lama.
ALTER TABLE users
ADD COLUMN first_name VARCHAR(100) NULL,
ADD COLUMN last_name VARCHAR(100) NULL;Kolom dibuat NULL pada tahap awal agar baris lama tetap valid. Jika versi MySQL dan kondisi tabel mendukungnya, Anda dapat mengevaluasi penggunaan ALGORITHM=INSTANT atau ALGORITHM=INPLACE. Jangan menyalin opsi tersebut secara buta; bila operasi tidak didukung, MySQL dapat menolak perintah atau memakai strategi yang lebih mahal.
2. Backfill: isi data lama secara bertahap
Setelah kolom tersedia, isi data lama dari name. Untuk tabel besar, hindari satu UPDATE raksasa yang berjalan lama dan membebani undo log. Kerjakan per batch berdasarkan primary key atau rentang waktu.
UPDATE users
SET first_name = ... , last_name = ...
WHERE id > 100000
AND id <= 110000
AND first_name IS NULL;Ukuran batch perlu diuji. Sepuluh ribu baris mungkin aman di server pengujian, tetapi terlalu besar pada server dengan banyak transaksi aktif. Pantau durasi query, penggunaan CPU, lock, dan dampaknya terhadap latency aplikasi.
3. Gunakan aplikasi untuk membaca keduanya
Pada fase transisi, kode PHP sebaiknya tidak menganggap data baru selalu tersedia. Saat membaca profil, gunakan kolom baru jika ada; jika belum, gunakan nilai lama sebagai fallback. Ini membuat deployment lebih lentur ketika proses backfill belum selesai.
$displayName = $user['first_name']
? trim($user['first_name'] . ' ' . $user['last_name'])
: $user['name'];Untuk penulisan data, aplikasi baru dapat menggunakan pola dual write: menyimpan nilai ke kolom baru dan tetap memperbarui kolom lama untuk sementara. Pola ini menambah kompleksitas, jadi gunakan hanya selama masa transisi dan beri batas waktu penghapusannya.
4. Contract: hapus bagian lama setelah aman
Kolom lama jangan langsung dihapus pada deployment yang sama. Pastikan semua endpoint, job, dashboard, dan script administratif sudah berhenti membacanya. Cari penggunaan nama kolom di seluruh repository, periksa query mentah, dan amati log aplikasi.
Setelah yakin tidak ada konsumen lama, barulah lakukan migrasi berikutnya untuk menghapus kolom atau constraint yang tidak diperlukan. Sebaiknya langkah ini dilakukan dalam release terpisah agar mudah diidentifikasi jika muncul masalah.
Bagaimana posisi transaksi PHP?
Transaksi tetap penting untuk perubahan data yang harus berhasil atau gagal sebagai satu kesatuan, misalnya membuat pesanan dan detail pesanan. Dengan PDO, pola umumnya adalah beginTransaction(), menjalankan query, lalu commit() atau rollBack(). Dokumentasi PDO menjelaskan batasan dan perilaku transaksi.
Namun, transaksi bukan tombol undo untuk semua jenis migrasi. Jangan menganggap kode berikut akan selalu membatalkan perubahan schema:
$pdo->beginTransaction();
$pdo->exec('UPDATE users SET status = 1');
$pdo->exec('ALTER TABLE users ADD COLUMN verified_at DATETIME NULL');
$pdo->rollBack();Pada MySQL, perintah DDL dapat menyebabkan commit implisit. Pisahkan migrasi schema dari transaksi data, lalu buat prosedur pemulihan yang realistis: backup, migrasi kebalikan jika memungkinkan, atau langkah perbaikan data yang sudah diuji.
Checklist sebelum migrasi production
- Uji migrasi pada salinan database dengan ukuran yang mendekati production.
- Periksa apakah operasi membutuhkan table rebuild atau metadata lock.
- Tambahkan timeout dan rencana penghentian jika lock terlalu lama.
- Pastikan aplikasi lama tetap kompatibel dengan schema setelah tahap expand.
- Jalankan backfill dalam batch dan ukur dampaknya.
- Simpan migration script di repository, bukan hanya di catatan pribadi.
- Siapkan backup serta prosedur pemulihan yang benar-benar pernah diuji.
- Hapus kolom lama hanya setelah seluruh pembaca dan penulisnya dipastikan sudah pindah.
Apa artinya bagi kita?
Migrasi aman bukan berarti setiap perubahan harus rumit. Intinya adalah memisahkan perubahan menjadi beberapa langkah kecil yang kompatibel. Dengan begitu, kode aplikasi dan database tidak perlu berubah secara drastis pada detik yang sama.
Untuk proyek kecil, pola ini mungkin terasa berlebihan. Namun, begitu website mulai menerima transaksi nyata, memiliki proses background, atau dikerjakan lebih dari satu orang, migrasi bertahap menjadi bentuk asuransi yang murah. Lima menit tambahan untuk memikirkan kompatibilitas dapat menghindarkan tim dari downtime, data yang tidak sinkron, dan rollback panik di tengah jam sibuk.
Sumber & bacaan lebih lanjut
- MySQL 8.0 Reference Manual: Online DDL Operations
- MySQL 8.0 Reference Manual: Statements That Cause an Implicit Commit
- PHP Manual: Transactions and auto-commit
– Rio Yotto @rioyotto
