Bayangkan kamu punya aplikasi Laravel yang melayani ribuan pengguna setiap hari. Tiba-tiba kamu perlu mengubah struktur database—namun kalau sampai aplikasi down walau cuma beberapa menit, bisa-bisa pengguna kabur dan bisnis rugi. Gimana cara mengatasinya?
Hari ini kita akan belajar teknik yang dipakai perusahaan besar seperti Shopify dan GitHub untuk mengubah database tanpa membuat aplikasi mati total. Tekniknya disebut Zero-Downtime Migration menggunakan strategi Double Write dan Backfill.
Kenapa Harus Belajar Zero-Downtime Migration?
Sebelum masuk ke teknis, mari kita pahami dulu mengapa ini penting. Kalau kamu jalanin migration biasa di aplikasi yang ramai, bisa terjadi:
- Table terkunci dan user dapat error “table is locked”
- Aplikasi down beberapa menit (atau lebih buruk lagi, berjam-jam)
- User kabur karena frustrasi
- Revenue turun drastis
Makanya, untuk aplikasi production yang sudah punya traffic tinggi, kita butuh cara yang lebih aman.
Apa Itu Double Write dan Backfill?
Konsep dasarnya sederhana: daripada langsung mengubah struktur database yang ada, kita buat struktur baru dulu, lalu secara bertahap memindahkan data. Ibaratnya seperti renovasi rumah—kamu nggak langsung bongkar semuanya, tapi bikin ruang baru dulu, baru kemudian pindah perabotan pelan-pelan.
Persiapan Sebelum Mulai
Pastikan kamu sudah punya:
- Laravel project yang sudah running
- Database dengan data yang cukup banyak (untuk testing)
- Environment staging untuk testing
- Backup database (ini wajib!)
Langkah 1: Tambah Kolom Baru Tanpa Hapus yang Lama
Misalnya kamu punya table products
seperti ini:
// Migration awal
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('product_name');
$table->integer('price'); // Ini yang mau diubah ke decimal
$table->timestamps();
});
Sekarang kamu mau:
- Tambah kolom
slug
- Ubah
price
dari integer ke decimal
Jangan langsung alter table! Buat migration baru yang menambah kolom, bukan mengubah:
// Migration baru - 2025_08_03_160000_add_new_columns_to_products.php
Schema::table('products', function (Blueprint $table) {
$table->string('slug')->nullable()->after('product_name');
$table->decimal('new_price', 10, 2)->nullable()->after('price');
});
Kenapa pakai nullable()
? Biar data yang sudah ada tetap bisa jalan normal tanpa error.
Tips Pro: Selalu Preview Dulu Migration
Sebelum jalanin migration, cek dulu SQL yang akan dieksekusi:
php artisan migrate --pretend
Command ini akan tampilkan SQL query yang akan dijalankan tanpa benar-benar menjalankannya. Ini penting banget untuk memastikan nggak ada query yang berbahaya.
Langkah 2: Implementasi Double Write
Sekarang kita perlu memastikan setiap kali ada perubahan data, kita tulis ke kolom lama DAN kolom baru. Di model Product, tambahkan:
class Product extends Model
{
protected $fillable = [
'product_name',
'price',
'new_price',
'slug'
];
// Event listener untuk sync data
protected static function booted()
{
static::saving(function ($product) {
// Kalau price berubah, sync ke new_price
if ($product->isDirty('price')) {
$product->new_price = $product->price;
}
// Auto-generate slug kalau product_name berubah
if ($product->isDirty('product_name')) {
$product->slug = Str::slug($product->product_name);
}
});
}
}
Dengan begini, setiap kali ada data baru atau update, otomatis tersync ke kolom baru.
Langkah 3: Backfill Data yang Sudah Ada
Nah, sekarang kita perlu isi kolom baru dengan data dari kolom lama. Buat Artisan command khusus:
// Buat command: php artisan make:command BackfillProductsCommand
// Atau langsung pakai closure command di routes/console.php
Artisan::command('backfill:products', function () {
$this->info('Memulai backfill data products...');
// Gunakan chunk untuk handle data besar
Product::whereNull('new_price') // Hanya yang belum diisi
->orWhereNull('slug')
->chunk(1000, function ($products) {
foreach ($products as $product) {
$product->update([
'new_price' => $product->price,
'slug' => Str::slug($product->product_name),
]);
}
$this->info("Processed {$products->count()} products...");
});
$this->info('Backfill selesai!');
});
Jalankan command:
php artisan backfill:products
Kenapa pakai chunk()
? Biar nggak kehabisan memory kalau datanya jutaan record.
Langkah 4: Siapkan Rencana Rollback
Ini yang sering dilupain tapi super penting! Selalu siapkan cara untuk mundur kalau ada masalah:
// Migration rollback
Schema::table('products', function (Blueprint $table) {
$table->dropColumn(['new_price', 'slug']);
});
Untuk rollback:
php artisan migrate:rollback --step=1
Langkah 5: Testing dan Monitoring
Sebelum lanjut ke production:
- Test di staging environment dengan data yang mirip production
- Monitor performa dengan Laravel Telescope atau tools monitoring lain
- Cek memory usage saat jalanin backfill
- Pastikan double write berjalan dengan coba create/update data
Langkah 6: Switch ke Struktur Baru
Kalau semua aman dan data sudah tersync sempurna:
- Update kode aplikasi untuk pakai kolom baru:
// Ubah semua reference dari 'price' ke 'new_price'
// Dan mulai pakai 'slug' dalam query
// Contoh di controller:
public function index()
{
return Product::select(['id', 'product_name', 'new_price as price', 'slug'])
->get();
}
- Buat migration final untuk hapus kolom lama:
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('price'); // Hapus kolom lama
$table->renameColumn('new_price', 'price'); // Rename ke nama asli
});
Switch Selesai, Zero Downtime!
Selamat! Kamu berhasil mengubah struktur database tanpa membuat aplikasi down sama sekali.
Tips Penting yang Wajib Diingat
✅ Selalu test di staging dulu - Jangan pernah langsung ke production
✅ Monitor performa - Pakai Laravel Telescope untuk pantau query
✅ Siapkan rollback plan - Better safe than sorry!
✅ Gunakan --pretend
- Preview migration sebelum jalanin
✅ Chunk data besar - Hindari memory overflow
✅ Backup database - Ini wajib hukumnya!
Kapan Sebaiknya Pakai Teknik Ini?
Teknik zero-downtime migration cocok untuk:
- Table dengan jutaan record
- Aplikasi dengan traffic tinggi yang nggak boleh down
- Perubahan tipe data kolom (misal: integer ke decimal)
- Menambah foreign key atau index di table besar
- Aplikasi e-commerce, banking, atau sistem critical lainnya
Kapan Nggak Perlu Ribet?
Kalau aplikasimu masih development atau traffic rendah, migration biasa sudah cukup. Teknik ini lebih untuk aplikasi production yang udah established.
Kesalahan yang Sering Terjadi Pemula
- Lupa backup database - Fatal error!
- Langsung alter column - Bisa bikin table lock
- Nggak test di staging - Recipe for disaster
- Lupa implement double write - Data jadi nggak sync
- Backfill data terlalu besar sekaligus - Server bisa hang
Tools yang Membantu
- Laravel Telescope: Monitor query dan performance
- Laravel Horizon: Kalau pakai queue untuk background processing
- Database monitoring tools: Untuk track performance database
Langkah Selanjutnya
Setelah menguasai basic zero-downtime migration, kamu bisa explore:
- Blue-green deployment
- Database sharding techniques
- Advanced indexing strategies
- Performance optimization untuk large datasets
Kesimpulan
Zero-downtime migration mungkin terlihat ribet di awal, tapi ini investment yang worth it untuk aplikasi production. Dengan teknik Double Write dan Backfill, kamu bisa dengan aman mengubah struktur database tanpa bikin user frustrasi karena downtime.
Ingat: “Measure twice, cut once” - selalu test dan planning matang sebelum eksekusi. Database adalah jantung aplikasi, jadi harus extra hati-hati.
Sekarang kamu sudah tahu caranya. Mulai dari aplikasi kecil dulu, baru nanti kalau sudah confidence, pakai di project besar. Happy coding!