Workflow deployment yang baik punya rencana B yang sudah diuji — saat insiden berlangsung bukan momen membaca dokumentasi sambil pelanggan mengeluh. Rollback mengembalikan production ke versi terakhir yang sehat, dan seperti deploy, hanya cepat bila urutan langkahnya pasti. Chapter ini menyusun keputusan kapan rollback layak dijalankan, membuktikan satu jebakan yang hampir selalu menjebak pemula — cache yang menahan kode baru tetap hidup — lalu menutup dengan pemulihan data dari backup.
Matriks Keputusan: Rollback atau Perbaiki #
Rollback bukan satu-satunya jalan keluar. Keputusannya ditentukan dua sumbu: seberapa parah dampak bug, dan berapa lama perbaikan yang diantisipasi:
| Dampak | Perbaikan bisa selesai | Perbaikan lama / belum jelas |
|---|---|---|
| Kecil (fitur non-kritis) | Hotfix forward di rilis berikutnya | Hotfix forward |
| Sedang (fitur utama terganggu) | Pertimbangkan rollback | Rollback |
| Besar (aplikasi tidak bisa dipakai) | Rollback, lalu perbaiki di versi lama | Rollback segera |
Prinsipnya: rollback mengembalikan pengalaman pengguna dengan cepat, perbaikan menyembuhkan penyebabnya — umumnya berurutan: rollback dulu, lalu perbaiki dengan kepala dingin.
Rollback Kode dan Jebakan Cache #
Mengembalikan kode berarti memproses versi lama melalui sebagian workflow deploy (contoh tampilan):
git fetch --tags
git checkout v1.4.2
composer install --no-dev --optimize-autoloader --no-interaction
php artisan optimize:clear && php artisan optimize
php artisan queue:restartBaris-baris artisan itulah yang sering dilupakan orang — dan kita buktikan mengapa ia wajib: routes-v7.php dan config.php di bootstrap/cache/ tidak ikut berubah saat kode di-checkout. Kita simulasikan sebuah rilis Toko Budi: halaman /stok sudah ter-cache dengan teks versi 1, lalu kode sumbernya diubah menjadi versi 2 — meniru kondisi “cache memuat rilis baru, kode di disk sudah lain”:
php artisan route:cache
curl -s http://127.0.0.1:8018/stokOutput yang Dihasilkan:
INFO Routes cached successfully.
Halaman stok — versi 1Sekarang kode di routes/web.php diedit menjadi versi 2 — setara dengan git checkout mengganti isi berkas — lalu halaman diminta lagi tanpa membangun ulang cache:
curl -s http://127.0.0.1:8018/stokOutput yang Dihasilkan:
Halaman stok — versi 1Server tetap menjawab versi 1 padahal berkas di disk berisi versi 2: PHP menjalankan hasil unserialize routes-v7.php, bukan kode — dan berkas itu baru berubah saat diperintahkan:
php artisan route:cache
curl -s http://127.0.0.1:8018/stokOutput yang Dihasilkan:
INFO Routes cached successfully.
Halaman stok — versi 2Pada kasus rollback, arah masalahnya terbalik: setelah git checkout v1.4.2, routes-v7.php masih memuat definisi rilis baru — aplikasi terus menjalankan kode yang tidak ada di disk sampai cache dibangun ulang. Config cache bersifat sama terhadap .env. Karena itu optimize:clear && optimize pada urutan rollback di atas bukan formalitas; tanpa queue:restart, worker juga masih menggenggam kelas rilis baru di memori.
Database: Mengapa migrate:rollback Biasanya Bukan Jawaban #
Jika rilis bermasalah disertai migrasi baru, tangan kita gatal menjalankan migrate:rollback demi “menyeimbangkan” — dan itu hampir selalu salah. Perintah tersebut memutar balik migrasi (dropColumn, dropTable), dan data yang masuk sejak rilis ikut lenyap, sebagaimana perilaku down() yang sudah kita lihat di Migration Rollback
. Strategi migrasi produksi yang aman (kolom baru dulu, isi belakangan, hapus di rilis berikutnya) sudah dibahas di Database Migration di Production
, dan strategi itu punya bonus: kode versi lama biasanya tetap kompatibel dengan skema baru, sehingga rollback kode tidak perlu menyentuh database sama sekali.
Bug diperbaiki dengan forward fix (rilis baru yang membetulkan), bukan memundurkan skema; rollback skema hanya relevan bila migrasinya sendiri yang rusak, dan saat itu backup adalah penyelamat terakhir.
Pemulihan dari Backup #
Backup yang tidak pernah dicoba dipulihkan hanyalah file penuh harapan. Kita uji siklusnya di sandbox: dua pengguna sebagai kondisi awal, backup berkas, rilis buruk yang menghapus data, lalu pemulihan:
use App\Models\User;
echo 'sebelum rilis : '.User::count()."\n";
copy(database_path('database.sqlite'), '/tmp/backup-pre-release.sqlite');
// ... rilis buruk terjadi ...
User::query()->delete();
echo 'setelah rilis rusak: '.User::count()."\n";
copy('/tmp/backup-pre-release.sqlite', database_path('database.sqlite'));
echo 'setelah restore : '.User::count()."\n";Output yang Dihasilkan:
sebelum rilis : 2 user
setelah rilis rusak: 0 user
setelah restore : 2 user
- Budi
- SitiSatu batasan: request yang menulis setelah backup diambil tidak ikut tersimpan — itulah mengapa pemulihan serius dilakukan dengan aplikasi dalam maintenance mode. Untuk MySQL di server produksi, pasangannya adalah mysqldump: backup diambil sebelum rilis berjalan, dan pemulihan menuliskannya kembali (contoh tampilan):
mysqldump --single-transaction -u tokobudi_app -p tokobudi_prod | gzip > backup-$(date +%F).sql.gz
gunzip < backup-2026-10-14.sql.gz | mysql -u tokobudi_app -p tokobudi_prodFlag --single-transaction mengambil snapshot konsisten tanpa mengunci tabel yang sedang melayani.
Rollback Atomik via Symlink #
Dengan struktur release directory dari Deployment Workflow
, rollback kode menjadi operasi satu detik tanpa git — rilis lama masih utuh di disk:
ln -sfn releases/2026-10-13-0900 current
php artisan queue:restartSymlink current dipindah secara atomik; document root Nginx yang mengarah ke current/public langsung melayani kode lama pada request berikutnya. Catatannya sama: migrasi tidak ikut mundur, dan cache tetap perlu dibangun ulang bila definisi route/config dua rilis berbeda.
Saat Rollback Justru Memperburuk #
Ada tiga kondisi menahan tombol rollback. Pertama, bila migrasi rilis baru sudah dieksekusi dan kode lama tidak kompatibel dengan skema baru (misalnya NOT NULL pada kolom yang kode lama biarkan kosong) — rollback kode menciptakan bug baru. Kedua, bila rilis bermasalah sudah berjalan berjam-jam dan data baru bernilai — pemulihan backup ikut membuangnya. Ketiga, bila penyebabnya bukan kode: server penuh atau database down tidak sembuh karena kode dimundurkan. Ketiganya menuntut forward fix.
Pasca-rollback, dua hal wajib menyusul: smoke test yang sama dengan pasca-deploy di Deployment Workflow untuk membuktikan versi lama sehat, dan pemantauan laju error beberapa jam ke depan. Tutup kejadian dengan postmortem lima menit tanpa menyalahkan siapa pun: apa yang terjadi, mengapa tidak ketemu lebih awal, dan satu perubahan konkret agar tidak terulang.
Dengan rencana B di tempatnya, satu hal terakhir yang memisahkan aplikasi siap publik dari eksperimen adalah daftar periksa keamanan — dibahas di Production Security Checklist .