Pengantar
Job Laravel Gagal karena loop retry tidak terkendali menyebabkan antrean stuck dan sumber daya berjibun digunakan, sehingga sistem tidak memproses event penting. Artikel ini langsung menjelaskan gejala sistem, diagnosa akar masalah, langkah debugging yang dilakukan, serta perbaikan konkret agar kasus serupa bisa dicegah.
Kamu akan melihat bagaimana log queue, metrik job, dan pengecekan konfigurasi Redis serta idempotensi job menyatu menjadi gambaran lengkap untuk memecahkan kasus ini.
Deskripsi Gejala
Gejala paling awal terlihat dari log queue worker:
Processing: App\Jobs\SyncInvoiceJob
Failed: App\Jobs\SyncInvoiceJob
Restarting job in 0 seconds (retry #1)Dengan interval sangat pendek, antrean membengkak: pending_jobs naik tajam di Horizon dashboard, latency request API pembuat invoice meningkat karena worker sibuk memproses job yang sama. Redis queue:default menunjukkan job id yang terus dikembalikan tanpa jeda, dan observability tools memperlihatkan CPU worker 90% karena proses retry berulang.
Diagnosa Loop Retry Job Laravel Gagal
Penyebab utama adalah kombinasi tiga faktor: konfigurasi Redis persistence yang tidak stabil, job yang tidak idempotent, dan locking gate yang tidak memblokir retry parallel.
- Redis misconfigured: Timeout client-output-buffer-limit terlalu rendah, menyebabkan worker kehilangan connection saat burst retry dan memaksa Laravel menganggap job gagal.
- Job tidak idempotent: SyncInvoiceJob memanggil API eksternal dan menulis ulang status tanpa pemeriksaan, sehingga seharusnya hanya jalan sekali per invoice.
- Gate locking lemah: Tidak ada locking di job, sehingga 3 worker memproses job serupa secara bersamaan; jika satu gagal, yang lain tetap memicu retry loop.
Kesimpulannya, Laravel menandai job gagal dan menambahkannya kembali ke queue karena retry policy default (try 1) menyalahkan Redis disconnect dan job yang selalu gagal akibat idempotensi tidak dijaga.
Langkah Debugging
1. Tracing Job dengan Laravel Telescope dan Horizon
Aktifkan Telescope untuk melihat lifecycle job. Dari sana terlihat bahwa SyncInvoiceJob berjilid di event JobFailed setiap 2 detik, tanpa tanggal finishing sukses. Horizon menunjukkan job ditambahkan kembali ke antrean oleh policy retry default, bukan karena exception kode bisnis.
2. Memeriksa Queue Policy
Cek config/queue.php dan jobs middleware. Retry policy default pada job ini tidak diatur khusus, sehingga Laravel menggunakan retry default 1 kali. Apabila Redis disconnect sesaat terjadi, job langsung ditandai gagal dan discored kembali. Untuk job ini seharusnya retryUntil atau timeout disesuaikan agar tidak loop jika recoverable.
3. Validasi Konfigurasi Redis dan Idempotensi
Redis mencatat OOM mencetak: "MISCONF Redis is configured not to allow reads during reconfiguration". Ini mengindikasikan Redis mengalami memory spike saat job banyak retry. Debugging menunjukkan job menulis ulang cache besar tanpa locking, membuat Redis ramai dan disconnect. Penambahan flag --tries=3 dan penguatan locking API (menggunakan cache lock per invoice id) memperlihatkan bahwa jika satu worker masih memproses, yang lain menunggu.
Perbaikan dan Observabilitas
Setelah menemukan akar masalah, langkah perbaikan dilakukan berurutan:
- Retry policy: Override method
retryUntil()agar job hanya di-retry selama 5 menit, plus gunakanbackoffeksplisit untuk menghindari loop dalam hitungan detik. - Error handling: Tambahkan try-catch pada
handle()job; jika API eksternal gagal dengan 5xx, log dan release job dengan delay 30 detik, bukan langsung gagal. Ini menghentikan loop karena job tidak langsung dicatat sebagai gagal. - Observabilitas: Pasang metric custom di Prometheus untuk retry count setiap job, dan buat alert jika terjadi spike. Logging ditambahkan agar setiap job mencatat id invoice dan status lock.
- Locking gate: Gunakan cache lock untuk memastikan hanya satu job menjalankan sinkronisasi per invoice. Contohnya:
public function handle()
{
$lock = Cache::lock('sync-invoice-'.$this->invoice->id, 30);
if (! $lock->get()) {
return $this->release(10);
}
try {
// panggil API eksternal, update status
} finally {
$lock->release();
}
}
Dengan pola ini, job lain yang mencoba bekerja pada invoice sama akan secara otomatis release dan menghindari duplikasi.
Pelajaran bagi Developer
Kasus ini mengingatkan bahwa job queue tidak hanya tentang retry, tapi juga tentang idempotensi, konfigurasi broker (Redis), dan observabilitas. Pastikan retry policy sesuai dengan karakteristik job, gunakan backoff untuk mencegah retry loop, dan tambahkan lock jika operasi tidak dapat dijalankan paralel. Terakhir, pantau metrik retry dan log failure agar gejala dini diketahui sebelum antrean membengkak.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!