Deployment Laravel yang aman menjadi lebih sulit ketika rilis tidak hanya mengubah kode aplikasi, tetapi juga skema database. Masalah paling umum adalah versi aplikasi lama dan versi skema baru tidak lagi kompatibel, sehingga rollback kode justru memicu error yang lebih parah.
Solusi yang lebih aman adalah menggabungkan feature flag, migrasi database yang backward-compatible, urutan deploy yang disiplin, dan rollback yang dirancang sejak awal. Dengan pendekatan ini, Anda bisa merilis perubahan secara bertahap, memantau dampaknya, lalu memutuskan apakah perlu melanjutkan, menonaktifkan fitur, melakukan forward fix, atau rollback aplikasi.
Mengapa deployment Laravel sering gagal saat ada perubahan database
Dalam banyak insiden produksi, akar masalahnya bukan semata pada bug aplikasi, tetapi pada ketidaksesuaian antara kode dan skema database. Contoh klasik:
- Aplikasi baru mulai menulis ke kolom baru, tetapi sebagian instance lama masih berjalan dan belum mengenali kolom tersebut.
- Migrasi menghapus atau mengganti nama kolom yang masih dipakai oleh kode lama.
- Perubahan tipe data atau constraint membuat data lama tidak lagi valid.
- Job queue yang tertunda diproses oleh worker dengan versi kode berbeda.
Karena itu, prinsip utamanya adalah: deploy kode dan database seolah-olah keduanya akan hidup berdampingan untuk sementara waktu. Jangan berasumsi semua request, worker, scheduler, dan proses background langsung berpindah ke versi baru pada saat yang sama.
Prinsip inti: expand, migrate, contract
Strategi paling aman untuk perubahan skema adalah pola expand - migrate - contract.
1. Expand
Tambahkan struktur baru tanpa merusak kompatibilitas lama. Misalnya menambah kolom nullable, tabel baru, atau index baru. Pada tahap ini, kode lama tetap bisa berjalan.
2. Migrate
Mulai pindahkan perilaku aplikasi secara bertahap. Aplikasi bisa membaca dari struktur lama dan baru, lalu menulis ke keduanya jika diperlukan. Di sinilah feature flag sangat berguna.
3. Contract
Setelah yakin tidak ada trafik yang bergantung pada struktur lama, barulah lakukan pembersihan: hapus kolom lama, hapus fallback, dan sederhanakan kode. Tahap ini sebaiknya dilakukan di rilis terpisah, bukan dalam satu deploy dengan perubahan fungsional utama.
Catatan penting: rollback database tidak selalu aman, terutama untuk migrasi destruktif seperti drop column, rename column, atau transformasi data irreversible. Dalam situasi seperti ini, forward fix sering lebih aman daripada memaksa rollback skema.
Feature flag untuk release bertahap di Laravel
Feature flag memisahkan deploy dari release. Artinya, kode baru bisa dikirim ke produksi lebih dulu dalam keadaan tidak aktif. Setelah verifikasi dasar selesai, fitur diaktifkan bertahap.
Kapan feature flag membantu
- Saat menambah alur bisnis baru yang bergantung pada kolom atau tabel baru.
- Saat ingin mengaktifkan fitur hanya untuk internal user atau sebagian trafik.
- Saat butuh cara cepat mematikan perilaku baru tanpa redeploy.
Contoh implementasi sederhana
Tanpa bergantung pada layanan eksternal, Anda bisa memulai dengan konfigurasi sederhana di Laravel. Untuk flag yang jarang berubah, pendekatan berbasis config atau environment cukup memadai. Untuk flag yang perlu diubah saat runtime, simpan di database atau cache.
<?php
return [
'new_checkout_flow' => env('FEATURE_NEW_CHECKOUT_FLOW', false),
'dual_write_orders' => env('FEATURE_DUAL_WRITE_ORDERS', false),
];Pemakaian di aplikasi:
<?php
if (config('features.new_checkout_flow')) {
return app(NewCheckoutService::class)->handle($request);
}
return app(LegacyCheckoutService::class)->handle($request);Untuk kebutuhan yang lebih praktis di produksi, buat pembungkus kecil agar evaluasi flag tidak tersebar di mana-mana:
<?php
namespace App\Support;
class Feature
{
public static function enabled(string $name): bool
{
return (bool) config("features.{$name}");
}
}Lalu gunakan:
<?php
use App\Support\Feature;
if (Feature::enabled('dual_write_orders')) {
// tulis ke struktur lama dan baru
}Trade-off feature flag
- Kelebihan: aktivasi bertahap, mitigasi cepat tanpa redeploy, lebih aman untuk eksperimen.
- Kekurangan: menambah kompleksitas kode, risiko flag terlupakan, dan kombinasi perilaku aplikasi menjadi lebih banyak untuk diuji.
Karena itu, setiap flag harus punya pemilik, tujuan, dan tanggal pembersihan. Flag sementara yang dibiarkan terlalu lama akan menjadi utang teknis.
Migrasi database yang backward-compatible di Laravel
Tujuan migrasi aman bukan sekadar membuat skema baru tersedia, tetapi memastikan versi aplikasi lama dan baru sama-sama tetap bisa bekerja selama transisi.
Contoh perubahan yang relatif aman
- Menambah kolom baru dengan nilai nullable.
- Menambah tabel baru yang belum dipakai oleh kode lama.
- Menambah index untuk mendukung query baru.
Contoh migrasi penambahan kolom:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('orders', function (Blueprint $table) {
$table->string('fulfillment_status')->nullable()->after('status');
});
}
public function down(): void
{
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn('fulfillment_status');
});
}
};Setelah kolom tersedia, jangan langsung mengasumsikan semua baris memiliki nilai baru. Kode baca harus tahan terhadap null atau fallback ke perilaku lama.
Dual write dan dual read
Jika Anda memindahkan data dari kolom lama ke kolom baru, biasanya dibutuhkan masa transisi:
- Dual write: saat update, aplikasi menulis ke dua tempat sekaligus.
- Dual read: saat baca, aplikasi lebih memilih sumber baru, tetapi fallback ke sumber lama jika data belum dimigrasikan.
Contoh sederhana pada model atau service layer:
<?php
$fulfillmentStatus = $order->fulfillment_status ?? $order->status;
if (Feature::enabled('dual_write_orders')) {
$order->status = $legacyStatus;
$order->fulfillment_status = $newStatus;
} else {
$order->status = $legacyStatus;
}
$order->save();Pola ini membuat rollback aplikasi lebih aman karena data penting masih tersedia di jalur lama selama transisi.
Hindari migrasi destruktif di rilis yang sama
Beberapa perubahan sebaiknya ditunda ke rilis berikutnya:
- Menghapus kolom atau tabel lama.
- Mengganti nama kolom yang masih dipakai kode lama.
- Mengubah constraint menjadi lebih ketat tanpa audit data.
- Mengubah tipe data yang berpotensi memotong atau merusak data.
Jika perubahan seperti ini dipaksakan dalam satu deploy, rollback aplikasi bisa gagal karena skema lama sudah hilang.
Urutan deploy Laravel yang mengurangi downtime
Urutan deploy yang aman biasanya lebih penting daripada alat deploy yang digunakan. Berikut alur praktis yang bisa diterapkan pada banyak lingkungan:
- Siapkan artefak rilis: build dependency, compile aset bila perlu, jalankan test dasar.
- Jalankan backup dan verifikasi restore plan: terutama jika ada migrasi berisiko.
- Deploy kode baru dalam keadaan fitur nonaktif.
- Jalankan migrasi yang backward-compatible.
- Reload PHP-FPM / worker / process manager agar kode baru benar-benar aktif.
- Restart queue worker dengan tertib agar job tidak diproses oleh kode lama terlalu lama.
- Lakukan health check.
- Aktifkan feature flag bertahap.
- Pantau metrik, log, error rate, dan query lambat.
- Lanjutkan rollout atau matikan flag jika ada gejala masalah.
Contoh urutan command
Perintah berikut hanya ilustrasi umum. Sesuaikan dengan pipeline, supervisor, queue driver, dan cara deploy Anda.
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
php artisan queue:restartMengapa queue restart penting? Karena worker Laravel yang sudah lama hidup dapat tetap memegang kode lama di memori. Jika ada perubahan payload job, model, atau akses kolom baru, worker lama bisa menjadi sumber error tersembunyi setelah deploy berhasil secara kasat mata.
Bagaimana jika menggunakan maintenance mode?
Untuk banyak kasus, deployment aman tidak harus memakai downtime penuh. Jika migrasi Anda backward-compatible dan rilis dilakukan bertahap, maintenance mode bisa dihindari. Namun, jika ada operasi yang benar-benar tidak kompatibel atau sangat sensitif, maintenance mode singkat mungkin lebih aman daripada membiarkan data korup.
Health check dasar setelah deploy
Health check tidak harus rumit, tetapi harus cukup untuk mendeteksi masalah utama: aplikasi tidak bisa hidup, koneksi database gagal, cache bermasalah, atau dependency penting tidak tersedia.
Contoh endpoint health check sederhana
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
Route::get('/health', function () {
try {
DB::select('SELECT 1');
return response()->json([
'status' => 'ok',
'app' => config('app.name'),
]);
} catch (\Throwable $e) {
return response()->json([
'status' => 'error',
'message' => 'dependency check failed',
], 500);
}
});Endpoint ini sengaja sederhana. Untuk internal monitoring, Anda bisa menambah pemeriksaan cache, queue backlog, atau koneksi Redis, tetapi jangan sampai health check justru menjadi endpoint yang berat.
Apa yang perlu dicek segera setelah rilis
- Status endpoint health check.
- Error 500 pada route kritis.
- Lonjakan exception di log aplikasi.
- Kegagalan job queue.
- Peningkatan latency atau query lambat.
- Penurunan success rate pada proses bisnis utama, misalnya checkout atau login.
Pemantauan metrik dan log setelah release
Setelah deploy, pekerjaan belum selesai. Banyak insiden baru terlihat beberapa menit kemudian saat trafik nyata mulai menyentuh path tertentu.
Metrik minimum yang sebaiknya dipantau
- Error rate: HTTP 5xx, exception, failure job queue.
- Latency: route utama, endpoint API penting, dan query database yang memburuk.
- Throughput: apakah volume request turun tidak wajar.
- Resource: CPU, memory, koneksi DB, disk, dan saturation worker.
- Business KPI: order sukses, login sukses, pembayaran sukses, bukan hanya metrik teknis.
Log yang perlu dicari
- Error seperti
Unknown column,Column not found, atau constraint violation. - Serialization/deserialization error pada job lama setelah perubahan payload.
- Mass assignment atau cast error akibat field baru.
- Timeout query setelah index belum tersedia atau query plan berubah.
Jika Anda menggunakan agregasi log, buat filter cepat untuk versi rilis terbaru, route kritis, dan exception yang baru muncul setelah waktu deploy.
Strategi rollback aplikasi tanpa memperburuk kondisi data
Rollback aplikasi aman hanya jika Anda sudah menyiapkan kompatibilitasnya. Tujuannya bukan sekadar mengembalikan kode lama, tetapi menjaga agar data yang sudah ditulis versi baru tidak membuat versi lama gagal.
Kapan rollback masuk akal
- Bug jelas berasal dari kode baru.
- Skema database masih kompatibel dengan versi aplikasi sebelumnya.
- Perubahan data belum bersifat destruktif atau irreversible.
Kapan rollback berbahaya
- Kolom lama sudah dihapus.
- Data sudah ditulis hanya ke format baru tanpa fallback.
- Migrasi melakukan transformasi data yang tidak bisa dibalikkan secara aman.
- Job dengan payload baru sudah menumpuk di queue dan akan dibaca kode lama.
Langkah rollback yang lebih aman
- Nonaktifkan feature flag baru lebih dulu jika memungkinkan.
- Hentikan atau drain komponen yang sensitif, misalnya worker queue tertentu.
- Rollback kode aplikasi ke release sebelumnya.
- Restart worker dan proses aplikasi agar tidak ada proses lama yang tertinggal.
- Verifikasi route kritis, queue, dan log error.
- Jangan buru-buru menjalankan rollback database kecuali benar-benar yakin aman.
Prinsip praktis: lebih aman rollback kode terlebih dahulu dan mempertahankan skema yang sudah diperluas, daripada rollback skema secara agresif. Skema hasil tahap expand umumnya masih bisa dipakai oleh aplikasi lama.
Kapan memilih forward fix
Forward fix lebih tepat jika:
- Masalah ada pada logika kecil yang bisa diperbaiki cepat.
- Data produksi sudah terlanjur menyesuaikan format baru.
- Rollback akan memutus kompatibilitas dengan migrasi yang sudah berjalan.
- Queue atau integrasi eksternal sudah terlanjur memproduksi payload versi baru.
Dalam situasi ini, tindakan paling aman sering kali: matikan flag, isolasi dampak, rilis perbaikan kecil, lalu aktifkan kembali bertahap.
Contoh alur deploy yang aman untuk perubahan skema
Misalkan Anda ingin memindahkan status pemenuhan order dari kolom lama status ke kolom baru fulfillment_status.
Rilis 1: expand
- Tambah kolom
fulfillment_statusnullable. - Deploy kode yang masih membaca
status, tetapi mampu fallback kefulfillment_statusjika ada. - Feature flag
dual_write_ordersmasih off.
Rilis 2: migrate
- Aktifkan
dual_write_ordersuntuk mulai menulis ke dua kolom. - Jalankan backfill data lama secara bertahap, misalnya dengan job batch.
- Pantau mismatch antara kolom lama dan baru.
Rilis 3: switch read path
- Aktifkan pembacaan utama dari
fulfillment_statusdengan fallback kestatus. - Pantau error, latency, dan konsistensi data.
Rilis 4: contract
- Setelah yakin semua pembacaan dan penulisan stabil, hentikan dual write.
- Hapus dependensi ke kolom lama dari kode.
- Di rilis berikutnya, hapus kolom
statusjika sudah benar-benar aman.
Dengan alur ini, rollback aplikasi pada rilis 2 atau 3 masih lebih mungkin aman karena kolom lama belum dihapus dan data masih tersedia.
Checklist pra-rilis
Sebelum deploy
- Apakah migrasi bersifat backward-compatible?
- Apakah ada perubahan destruktif yang bisa dipisah ke rilis lain?
- Apakah feature flag default-nya aman?
- Apakah rollback kode masih kompatibel dengan skema baru?
- Apakah job queue, scheduler, dan worker sudah diperhitungkan?
- Apakah ada backup dan rencana restore yang realistis?
- Apakah route atau KPI kritis yang harus dipantau sudah ditentukan?
- Apakah backfill data diuji pada data produksi yang representatif?
- Apakah threshold alert setelah deploy sudah jelas?
Sebelum mengaktifkan flag
- Health check lulus.
- Migrasi selesai tanpa error.
- Worker sudah direstart.
- Log tidak menunjukkan anomali baru.
- Metrik dasar stabil selama beberapa menit pertama.
Checklist pasca-rilis
- Periksa error rate, log exception, dan response time.
- Periksa queue backlog dan job failure.
- Verifikasi alur bisnis utama dengan data nyata atau smoke test terbatas.
- Aktifkan flag bertahap, jangan sekaligus jika risikonya tinggi.
- Bandingkan metrik sebelum dan sesudah rilis.
- Catat keputusan: lanjut rollout, matikan flag, rollback kode, atau siapkan forward fix.
Contoh insiden ringan dan postmortem singkat
Insiden: Setelah deploy, endpoint pembuatan order mulai menghasilkan error 500. Log menunjukkan SQLSTATE terkait kolom fulfillment_status yang tidak ditemukan pada sebagian request.
Kronologi singkat
- Kode baru dideploy ke beberapa instance.
- Feature flag untuk alur baru aktif terlalu cepat.
- Sebagian worker atau instance lama masih berjalan dan sebagian request diarahkan ke kode baru sebelum migrasi benar-benar selesai di seluruh lingkungan.
Dampak
- Order gagal dibuat pada sebagian trafik.
- Job retry meningkat karena exception berulang.
Akar masalah
- Urutan deploy salah: flag diaktifkan sebelum verifikasi migrasi dan restart worker selesai.
- Kode baru tidak punya fallback aman ketika kolom baru belum tersedia.
Respons yang benar
- Matikan feature flag untuk alur baru.
- Pastikan migrasi selesai dan worker direstart.
- Verifikasi bahwa route kritis kembali normal.
- Rilis perbaikan agar pembacaan bersifat defensif selama masa transisi.
Tindakan pencegahan ke depan
- Aktivasi flag hanya setelah checklist deploy lulus.
- Tambahkan smoke test yang menyentuh path dengan kolom baru.
- Wajibkan fallback untuk field baru selama minimal satu siklus rilis.
- Dokumentasikan dependency antara migrasi, queue worker, dan flag.
Kesalahan umum yang perlu dihindari
- Menjalankan migrasi destruktif dan perubahan kode dalam satu langkah tanpa transisi.
- Menganggap rollback database selalu tersedia dan aman.
- Melupakan queue worker yang masih menjalankan kode lama.
- Menyimpan keputusan flag di banyak tempat tanpa abstraksi yang jelas.
- Tidak menyiapkan observability sehingga masalah baru terlihat terlalu lambat.
- Tidak membersihkan feature flag setelah rollout selesai.
Penutup
Deployment Laravel yang aman saat ada perubahan aplikasi dan database tidak bergantung pada satu trik, tetapi pada disiplin desain rilis. Gunakan feature flag untuk memisahkan deploy dari release, buat migrasi backward-compatible, urutkan langkah deploy dengan hati-hati, lalu pantau sistem dengan metrik dan log yang relevan.
Jika terjadi masalah, jangan otomatis melakukan rollback database. Evaluasi dulu apakah rollback kode cukup aman, apakah feature flag bisa mematikan dampak, dan apakah forward fix justru lebih tepat. Pendekatan ini biasanya menghasilkan rilis yang lebih stabil, downtime lebih rendah, dan risiko kerusakan data yang jauh lebih kecil.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!