Laravel Queue Delay dan Retry: Mengatur Job yang Perlu Dijadwalkan Ulang

Apa Itu Queue Delay dan Retry di Laravel?

Laravel Queue memungkinkan aplikasi menjalankan pekerjaan di background menggunakan Job.

Dalam kondisi tertentu, Job tidak harus langsung dijalankan.

Misalnya:

  • Mengirim email 5 menit setelah user mendaftar
  • Mengirim reminder beberapa jam kemudian
  • Menunggu beberapa saat sebelum memanggil API lagi
  • Mencoba kembali Job yang gagal
  • Memberikan jeda ketika API eksternal mengalami masalah sementara

Laravel menyediakan dua konsep penting untuk kebutuhan tersebut:

Delay digunakan untuk menentukan kapan Job mulai tersedia untuk diproses.

Retry digunakan ketika Job gagal dan perlu dicoba kembali.

Secara sederhana:

Delay

Dispatch Job
     ↓
Menunggu
     ↓
Queue Worker
     ↓
Proses Job

Sedangkan Retry:

Job
 ↓
Gagal
 ↓
Retry
 ↓
Gagal
 ↓
Retry
 ↓
Berhasil

Kedua fitur ini sangat berguna ketika aplikasi mulai menggunakan Queue untuk email, API, import data, notifikasi, dan proses background lainnya.

Mengapa Job Perlu Menggunakan Delay?

Tidak semua pekerjaan harus langsung dilakukan.

Misalnya setelah user melakukan registrasi:

User Register
     ↓
Akun berhasil dibuat
     ↓
Kirim Welcome Email

Jika email langsung dikirim, mungkin tidak ada masalah.

Tetapi untuk beberapa kebutuhan, kita mungkin ingin memberikan jeda:

User Register
     ↓
Akun berhasil dibuat
     ↓
Delay 5 menit
     ↓
Kirim Welcome Email

Contoh lainnya adalah reminder.

Order Dibuat
     ↓
Delay 24 Jam
     ↓
Cek Status Order
     ↓
Kirim Reminder jika diperlukan

Dengan Queue Delay, aplikasi tidak perlu menggunakan cron atau scheduler untuk setiap pekerjaan yang hanya membutuhkan penundaan sederhana.

Cara Memberikan Delay pada Laravel Job

Cara paling sederhana adalah menggunakan delay() ketika melakukan dispatch.

Contoh:

SendWelcomeEmail::dispatch($user)
    ->delay(now()->addMinutes(5));

Job tersebut tidak langsung tersedia untuk diproses.

Laravel akan menunggu sampai waktu yang ditentukan sebelum Job dapat diambil oleh Worker.

Contoh delay 1 jam:

SendWelcomeEmail::dispatch($user)
    ->delay(now()->addHour());

Atau:

SendWelcomeEmail::dispatch($user)
    ->delay(now()->addHours(1));

Delay dengan Waktu Tertentu

Delay juga dapat menggunakan waktu tertentu.

Contohnya:

SendReminder::dispatch($order)
    ->delay(now()->addDay());

Atau menggunakan waktu yang sudah dihitung:

$sendAt = now()->addHours(3);

SendReminder::dispatch($order)
    ->delay($sendAt);

Ini berguna ketika waktu eksekusi ditentukan berdasarkan kondisi bisnis.

Delay Berdasarkan Data

Misalnya setiap order memiliki waktu reminder:

$sendAt = $order->reminder_at;

SendOrderReminder::dispatch($order)
    ->delay($sendAt);

Dengan demikian, setiap Job dapat memiliki jadwal yang berbeda.

Contohnya:

Order #101 → 10:00
Order #102 → 11:00
Order #103 → 13:00

Queue Worker akan mengambil Job ketika waktunya sudah tersedia.

Delay pada Constructor Job

Delay juga dapat ditentukan ketika membuat Job.

Contoh:

class SendReminder implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public Order $order
    ) {}

    public function handle(): void
    {
        // Kirim reminder
    }
}

Kemudian:

SendReminder::dispatch($order)
    ->delay(now()->addHours(2));

Pendekatan ini biasanya lebih fleksibel karena waktu delay dapat ditentukan oleh bagian aplikasi yang melakukan dispatch.

Delay vs Laravel Scheduler

Delay dan Scheduler memiliki tujuan yang berbeda.

Queue Delay cocok ketika kita ingin menjadwalkan satu Job tertentu untuk diproses nanti.

Laravel Scheduler cocok untuk menjalankan task berdasarkan jadwal berulang.

Contohnya:

Queue Delay
Order dibuat
    ↓
Delay 2 jam
    ↓
SendReminder Job

Sedangkan Scheduler:

Setiap hari pukul 08:00
    ↓
Jalankan task
    ↓
Generate Report

Jadi jangan menganggap Queue Delay sebagai pengganti Scheduler.

Apa Itu Retry pada Laravel Queue?

Retry berarti menjalankan kembali Job yang gagal.

Contohnya:

Job
 ↓
API Request
 ↓
Timeout
 ↓
Retry
 ↓
API Request
 ↓
Berhasil

Retry sangat berguna untuk error yang sifatnya sementara.

Contohnya:

  • API eksternal timeout
  • Network connection gagal
  • Service sementara tidak tersedia
  • Database mengalami gangguan sementara
  • Rate limit
  • Resource eksternal belum siap

Namun, tidak semua error harus di-retry.

Jika error disebabkan bug permanen, retry berulang kali hanya membuang resource.

Mengatur Jumlah Retry dengan $tries

Kita dapat menentukan jumlah percobaan Job.

Contoh:

class ProcessOrder implements ShouldQueue
{
    public $tries = 3;

    public function handle(): void
    {
        // Proses Job
    }
}

Dengan konfigurasi tersebut, Laravel dapat mencoba Job beberapa kali sebelum akhirnya dianggap gagal.

Secara konsep:

Attempt 1
   ↓
Gagal
   ↓
Attempt 2
   ↓
Gagal
   ↓
Attempt 3
   ↓
Gagal
   ↓
Failed Job

Jumlah percobaan harus disesuaikan dengan jenis pekerjaan.

Retry Tidak Selalu Berarti Error Harus Hilang

Misalnya Job melakukan request ke API:

$response = Http::timeout(10)
    ->post($url, $data);

Jika API timeout, retry mungkin membantu.

Tetapi jika request gagal karena:

HTTP 400
Invalid data

retry kemungkinan tidak akan memperbaiki masalah.

Karena data request tetap salah.

Karena itu, strategi retry harus membedakan antara:

Transient Error
→ Layak di-retry

dan:

Permanent Error
→ Perlu diperbaiki atau ditandai gagal

Menggunakan backoff untuk Retry

Jangan selalu melakukan retry secara langsung.

Bayangkan API eksternal sedang mengalami gangguan.

Jika 1.000 Job gagal lalu semuanya langsung retry:

1.000 Job gagal
      ↓
1.000 Retry sekaligus
      ↓
API semakin terbebani

Kondisi seperti ini dapat memperparah masalah.

Kita dapat menggunakan backoff untuk memberikan jeda sebelum retry.

Contoh:

class SyncProduct implements ShouldQueue
{
    public $tries = 3;

    public $backoff = 10;

    public function handle(): void
    {
        // Sinkronisasi produk
    }
}

Artinya Job memiliki jeda sebelum percobaan ulang sesuai konfigurasi backoff.

Backoff Bertahap

Kita juga dapat menggunakan array untuk menentukan jeda retry.

Contoh:

public function backoff(): array
{
    return [10, 30, 60];
}

Konsepnya:

Attempt 1
   ↓
Gagal
   ↓
Tunggu 10 detik
   ↓
Attempt 2
   ↓
Gagal
   ↓
Tunggu 30 detik
   ↓
Attempt 3
   ↓
Gagal
   ↓
Tunggu 60 detik

Format dan perilaku detail dapat bergantung pada versi Laravel yang digunakan, jadi pastikan implementasi mengikuti dokumentasi versi Laravel project.

Exponential Backoff

Untuk API eksternal atau service yang mengalami gangguan, exponential backoff sering menjadi strategi yang baik.

Contohnya:

10 detik
30 detik
60 detik
120 detik

Semakin banyak percobaan, semakin panjang jedanya.

Tujuannya adalah memberikan waktu kepada service yang bermasalah untuk pulih.

Contoh implementasi dinamis:

public function backoff(): array
{
    return [10, 30, 60, 120];
}

Jika jumlah retry hanya tiga, tidak semua nilai tersebut tentu akan digunakan.

Retry dengan Delay yang Berbeda

Misalnya kita memiliki Job:

class SyncProduct implements ShouldQueue
{
    public $tries = 4;

    public function backoff(): array
    {
        return [10, 30, 60];
    }

    public function handle(): void
    {
        // Sync product
    }
}

Alurnya:

Attempt 1
    ↓
Gagal
    ↓
10 detik
    ↓
Attempt 2
    ↓
Gagal
    ↓
30 detik
    ↓
Attempt 3
    ↓
Gagal
    ↓
60 detik
    ↓
Attempt 4

Setelah percobaan terakhir gagal, Job dapat masuk ke failed jobs sesuai konfigurasi aplikasi.

Retry dan Timeout Harus Dipikirkan Bersama

timeout menentukan berapa lama sebuah Job boleh berjalan.

Contoh:

public $timeout = 120;

Artinya Job memiliki batas waktu sekitar 120 detik.

Sedangkan:

public $tries = 3;

mengatur jumlah percobaan.

Keduanya berbeda:

Timeout
→ Berapa lama satu attempt boleh berjalan?

Tries
→ Berapa kali Job boleh dicoba?

Misalnya:

tries = 3
timeout = 120

Secara kasar, satu Job dapat memiliki beberapa attempt dengan masing-masing batas waktu tersebut, ditambah waktu backoff dan overhead lainnya.

Karena itu, jangan menentukan timeout dan retry tanpa mempertimbangkan total waktu yang mungkin dibutuhkan.

Retry untuk API Eksternal

API eksternal merupakan salah satu contoh paling cocok untuk retry.

Misalnya:

$response = Http::timeout(10)
    ->get($url);

Jika API mengalami timeout sementara, Job dapat dicoba kembali.

Contoh:

class SyncProduct implements ShouldQueue
{
    public $tries = 3;

    public $timeout = 30;

    public function backoff(): array
    {
        return [10, 30];
    }

    public function handle(): void
    {
        Http::timeout(10)
            ->get('https://example.com/api/products');
    }
}

Alurnya:

Request API
   ↓
Timeout
   ↓
Tunggu 10 detik
   ↓
Retry
   ↓
Gagal
   ↓
Tunggu 30 detik
   ↓
Retry

Untuk implementasi production, sebaiknya tambahkan penanganan response HTTP dan bedakan error yang memang layak di-retry.

Retry dan HTTP Status Code

Tidak semua HTTP status code sebaiknya di-retry.

Contoh:

429 Too Many Requests
→ Sering layak di-retry

500 Internal Server Error
→ Dapat layak di-retry

502 Bad Gateway
→ Dapat layak di-retry

503 Service Unavailable
→ Dapat layak di-retry

400 Bad Request
→ Biasanya tidak perlu retry

401 Unauthorized
→ Perbaiki authentication

404 Not Found
→ Belum tentu perlu retry

Strategi sebenarnya bergantung pada API yang digunakan.

Jika API memberikan informasi Retry-After, aplikasi juga sebaiknya mempertimbangkan nilai tersebut.

Retry-After dari API

Beberapa API dapat mengembalikan header:

Retry-After

Misalnya:

HTTP 429
Retry-After: 60

Artinya server meminta client menunggu sekitar 60 detik sebelum mencoba lagi.

Untuk integrasi API production, lebih baik menghormati mekanisme seperti ini daripada melakukan retry secara agresif.

Menangani Rate Limit

Misalnya aplikasi mengirim terlalu banyak request:

Laravel Queue
 ↓
API
 ↓
429 Too Many Requests

Jika semua Job langsung retry:

429
 ↓
Retry
 ↓
429
 ↓
Retry
 ↓
429

Ini tidak efektif.

Gunakan:

  • Backoff
  • Rate limiting
  • Delay
  • Queue throttling
  • Concurrency control

Dengan demikian request dapat diproses secara lebih terkontrol.

Job Retry dan Idempotency

Retry membawa satu risiko penting: Job dapat dijalankan lebih dari satu kali.

Contoh:

Process Payment
     ↓
Payment berhasil
     ↓
Response timeout
     ↓
Job menganggap gagal
     ↓
Retry

Jika tidak ada perlindungan, pembayaran dapat dilakukan dua kali.

Karena itu, Job yang memiliki efek samping harus dirancang idempotent.

Contoh gunakan transaction reference:

transaction_id = INV-2026-001

Sebelum melakukan proses:

Apakah INV-2026-001 sudah diproses?
        │
      Ya ───→ Jangan proses lagi
        │
     Tidak
        ↓
    Proses

Ini sangat penting untuk:

  • Payment
  • Order
  • Invoice
  • Sinkronisasi data
  • Pengiriman request ke API
  • Perubahan status transaksi

Jangan Retry Error Permanen

Misalnya Job menjalankan:

$order->update([
    'status' => 'completed',
]);

tetapi ternyata $order tidak ditemukan.

Retry tiga kali tidak akan membuat order tiba-tiba muncul.

Error seperti ini lebih baik diperbaiki atau ditangani sebagai kondisi bisnis daripada terus di-retry.

Contoh lainnya:

Invalid API credential

Tidak ada gunanya melakukan:

Retry 1
Retry 2
Retry 3
Retry 4
...

sampai credential diperbaiki.

Menangani Failed Job

Jika seluruh percobaan gagal, Job dapat masuk ke daftar failed jobs.

Kita dapat melihatnya dengan:

php artisan queue:failed

Contoh:

2026-08-23 01:00:00
Job ID: 123
Connection: redis
Queue: default
Job: App\Jobs\SyncProduct

Setelah penyebab error diperbaiki, Job dapat dicoba kembali:

php artisan queue:retry 123

ID disesuaikan dengan Job yang ingin di-retry.

Retry Semua Failed Jobs

Jika memang diperlukan, Laravel juga menyediakan mekanisme untuk mencoba kembali beberapa atau seluruh failed jobs sesuai command yang tersedia pada versi Laravel.

Namun, jangan melakukan retry massal tanpa memahami penyebab kegagalan.

Misalnya 10.000 Job gagal karena API sedang down.

Jika semuanya langsung di-retry:

10.000 Failed Jobs
       ↓
10.000 Retry
       ↓
API Overload

Lebih baik perbaiki penyebabnya terlebih dahulu dan lakukan retry secara terkontrol.

Menghapus Failed Job

Jika Job tidak perlu diproses kembali, kita dapat menghapusnya menggunakan command yang tersedia:

php artisan queue:forget <id>

Gunakan ID failed Job yang sesuai.

Untuk menghapus seluruh failed jobs, Laravel juga memiliki command terkait. Sebelum menjalankannya di production, pastikan Job memang tidak lagi dibutuhkan.

Method failed()

Kita dapat menjalankan logic tertentu ketika Job akhirnya gagal.

Contoh:

public function failed(?Throwable $exception): void
{
    Log::error('Sync product gagal', [
        'product_id' => $this->productId,
        'error' => $exception?->getMessage(),
    ]);
}

Ini berguna untuk:

  • Logging
  • Mengubah status menjadi failed
  • Mengirim notifikasi admin
  • Mencatat alasan kegagalan
  • Membuat audit trail

Contoh Job dengan Delay, Retry, Backoff, dan Timeout

Berikut contoh Job yang menggabungkan beberapa fitur:

<?php

namespace App\Jobs;

use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

class SyncProduct implements ShouldQueue
{
    use Queueable;

    public $tries = 3;

    public $timeout = 60;

    public function __construct(
        public Product $product
    ) {}

    public function backoff(): array
    {
        return [10, 30];
    }

    public function handle(): void
    {
        $response = Http::timeout(30)
            ->post('https://example.com/api/products', [
                'id' => $this->product->id,
                'name' => $this->product->name,
            ]);

        $response->throw();
    }

    public function failed(?Throwable $exception): void
    {
        Log::error('Product sync failed', [
            'product_id' => $this->product->id,
            'error' => $exception?->getMessage(),
        ]);
    }
}

Dispatch langsung:

SyncProduct::dispatch($product);

Atau dengan delay:

SyncProduct::dispatch($product)
    ->delay(now()->addMinutes(5));

Alurnya:

Dispatch
   ↓
Delay 5 menit
   ↓
Attempt 1
   ↓
Gagal
   ↓
10 detik
   ↓
Attempt 2
   ↓
Gagal
   ↓
30 detik
   ↓
Attempt 3
   ↓
Gagal
   ↓
failed()

Delay pada Job Tidak Berarti Worker Berhenti

Ini konsep yang penting.

Jika Job memiliki delay:

SendReminder::dispatch($order)
    ->delay(now()->addHour());

Worker tidak harus berhenti menunggu satu Job tersebut.

Worker dapat memproses Job lain yang sudah tersedia.

Contohnya:

Queue
 ├── Reminder A → Delay 1 jam
 ├── Email B → Ready
 └── Order C → Ready

Worker dapat memproses:

Email B
   ↓
Order C
   ↓
...

sementara Reminder A menunggu sampai waktunya tersedia.

Delay vs Retry Backoff

Keduanya sama-sama melibatkan waktu tunggu, tetapi konteksnya berbeda.

Delay:

Job::dispatch()
    ->delay(now()->addMinutes(10));

Digunakan untuk menunda Job sebelum pertama kali diproses.

Backoff:

public function backoff(): array
{
    return [10, 30];
}

Digunakan untuk menentukan jeda sebelum percobaan ulang setelah Job gagal.

Jadi:

Delay
→ Kapan Job pertama kali boleh diproses?

Backoff
→ Berapa lama menunggu setelah Job gagal?

Delay, Retry, dan Scheduler

Ketiganya sering dianggap sama, padahal berbeda.

FiturFungsi
Queue DelayMenunda Job tertentu
Queue RetryMengulang Job yang gagal
BackoffMengatur jeda sebelum retry
SchedulerMenjalankan task berdasarkan jadwal
Queue WorkerMemproses Job dari Queue

Contohnya:

Scheduler
   ↓
Setiap hari pukul 08:00
   ↓
Dispatch Job
   ↓
Queue
   ↓
Worker
   ↓
Process Job

Sedangkan:

Controller
   ↓
Dispatch Job
   ↓
Delay 1 jam
   ↓
Queue Worker
   ↓
Process Job

Mengatur Retry pada Command Queue Worker

Selain konfigurasi pada Job, Worker juga memiliki opsi terkait retry dan timeout.

Contoh:

php artisan queue:work redis --tries=3 --timeout=120

Dengan command tersebut, Worker dijalankan menggunakan Redis dan diberikan konfigurasi retry serta timeout.

Konfigurasi pada Job dan Worker dapat berinteraksi. Karena itu, pastikan memahami konfigurasi project agar tidak terjadi konflik atau ekspektasi yang berbeda.

Jangan Menyamakan $tries dengan --tries

Kamu mungkin menemukan dua bentuk konfigurasi:

Pada Job:

public $tries = 3;

Pada Worker:

php artisan queue:work --tries=3

Keduanya berkaitan dengan jumlah percobaan, tetapi memiliki konteks konfigurasi berbeda.

Untuk konfigurasi yang spesifik terhadap Job, mendefinisikannya pada Job dapat membuat perilakunya lebih jelas.

Contohnya:

class SyncProduct implements ShouldQueue
{
    public $tries = 5;
}

Job tersebut memiliki kebutuhan retry sendiri.

Mengatur Retry Berdasarkan Waktu

Laravel juga mendukung konsep membatasi retry berdasarkan waktu melalui konfigurasi Job tertentu.

Misalnya daripada menentukan jumlah percobaan:

public $tries = 10;

kita dapat menggunakan batas waktu retry sesuai kebutuhan.

Konsepnya:

Selama belum melewati batas waktu
        ↓
Boleh retry

Setelah batas waktu
        ↓
Failed

Fitur yang tersedia dan detail implementasinya dapat berbeda berdasarkan versi Laravel.

Untuk project production, pastikan mengacu pada API Queue versi Laravel yang sedang digunakan.

Retry yang Aman untuk Database

Misalnya Job memperbarui database:

public function handle(): void
{
    $order = Order::findOrFail($this->orderId);

    $order->update([
        'status' => 'processed',
    ]);
}

Jika Job gagal setelah update tetapi sebelum menyelesaikan proses lainnya, retry dapat menjalankan update kembali.

Untuk operasi kompleks, gunakan transaction atau desain idempotency agar retry tidak menyebabkan kondisi data yang tidak diinginkan.

Contohnya:

DB::transaction(function () {
    // Proses database
});

Namun transaction tidak otomatis membuat seluruh Job idempotent. Efek samping eksternal seperti email atau API tetap perlu dipikirkan secara terpisah.

Queue Retry dan Email

Email merupakan kasus yang menarik.

Misalnya:

Send Invoice Email
     ↓
SMTP Timeout
     ↓
Retry

Retry dapat membantu jika SMTP hanya mengalami gangguan sementara.

Tetapi jika email sudah diterima oleh mail server namun response timeout terjadi, retry dapat berpotensi mengirim email dua kali.

Untuk email penting, pertimbangkan:

  • Unique message/reference ID
  • Status pengiriman
  • Idempotency
  • Mail provider yang mendukung tracking
  • Mekanisme deduplikasi

Queue Retry dan API Payment

Payment membutuhkan perhatian lebih besar.

Jangan membuat Job seperti:

public function handle()
{
    PaymentGateway::charge($this->amount);
}

kemudian membiarkan Laravel retry secara otomatis tanpa idempotency.

Lebih aman menggunakan payment reference unik:

ORDER-2026-001

Kemudian API payment gateway dapat menggunakan reference tersebut untuk memastikan request yang sama tidak diproses dua kali jika fitur tersebut didukung.

Konsep pentingnya:

Retry ≠ Aman secara otomatis

Developer harus merancang Job agar retry tidak menyebabkan efek samping ganda.

Monitoring Retry

Jika sebuah Job terus-menerus retry, itu merupakan sinyal bahwa ada masalah.

Misalnya:

SyncProduct
 ↓
Retry 1
 ↓
Retry 2
 ↓
Retry 3
 ↓
Failed

Jika terjadi pada ribuan Job, cari penyebab utamanya.

Kemungkinan:

  • API down
  • Redis bermasalah
  • Database lambat
  • Credential expired
  • Bug aplikasi
  • Rate limit
  • Network issue

Jangan hanya menaikkan:

public $tries = 20;

tanpa mencari penyebab kegagalan.

Laravel Horizon untuk Monitoring Queue Redis

Jika aplikasi menggunakan Redis sebagai Queue backend, Laravel Horizon dapat membantu memonitor Queue.

Horizon menyediakan informasi mengenai:

  • Job yang sedang diproses
  • Job yang selesai
  • Failed jobs
  • Runtime
  • Queue throughput
  • Worker

Ini sangat berguna ketika aplikasi memiliki banyak Job dan retry.

Dengan monitoring, kita dapat melihat apakah suatu Job sering gagal dan membutuhkan retry berulang.

Contoh Strategi Retry untuk API

Untuk API yang dapat mengalami gangguan sementara, strategi sederhana dapat berupa:

Attempt 1
 ↓
Gagal
 ↓
10 detik

Attempt 2
 ↓
Gagal
 ↓
30 detik

Attempt 3
 ↓
Gagal
 ↓
Failed

Implementasi:

public $tries = 3;

public function backoff(): array
{
    return [10, 30];
}

Ini jauh lebih baik daripada:

Attempt 1
 ↓
Gagal
 ↓
Retry langsung

Attempt 2
 ↓
Gagal
 ↓
Retry langsung

terutama ketika error berasal dari service eksternal yang sedang bermasalah.

Best Practice Laravel Queue Delay dan Retry

Beberapa praktik yang sebaiknya diterapkan:

Gunakan Delay untuk Pekerjaan yang Memang Tidak Perlu Langsung Diproses

Contoh:

SendReminder::dispatch($order)
    ->delay(now()->addHours(2));

Gunakan Retry untuk Error Transient

Contohnya:

  • Timeout
  • Connection error
  • HTTP 502
  • HTTP 503
  • Rate limit

Jangan Retry Error Permanen

Contohnya:

  • Data invalid
  • Credential salah
  • Resource memang tidak ditemukan

Gunakan Backoff

Contoh:

public function backoff(): array
{
    return [10, 30, 60];
}

Tentukan Timeout yang Masuk Akal

public $timeout = 120;

Jangan menggunakan timeout terlalu tinggi hanya untuk menyembunyikan Job yang lambat.

Rancang Job agar Idempotent

Terutama untuk:

  • Payment
  • Order
  • Invoice
  • API
  • Sinkronisasi

Monitor Failed Jobs

Gunakan:

php artisan queue:failed

dan monitoring seperti Horizon jika menggunakan Redis.

Jangan Retry Secara Membabi Buta

Jika ribuan Job gagal karena service eksternal down, retry massal dapat memperparah masalah.

Contoh Alur Production

Untuk aplikasi Laravel dengan Redis:

                    Laravel
                       │
                       ↓
                 Dispatch Job
                       │
                       ↓
                     Redis
                       │
                       ↓
                  Queue Worker
                       │
                 ┌─────┴─────┐
                 │           │
              Success      Failed
                 │           │
                 ↓           ↓
              Selesai      Backoff
                              │
                              ↓
                           Retry
                              │
                    ┌─────────┴─────────┐
                    ↓                   ↓
                 Success              Failed
                    │                   │
                    ↓                   ↓
                 Selesai           Failed Jobs

Dengan arsitektur ini, Laravel dapat menangani pekerjaan background dengan lebih terkontrol.

Kesimpulan

Laravel Queue Delay dan Retry membantu mengatur kapan sebuah Job diproses dan apa yang harus dilakukan ketika Job mengalami kegagalan.

Untuk menunda Job:

SendReminder::dispatch($order)
    ->delay(now()->addHours(2));

Untuk membatasi percobaan:

public $tries = 3;

Untuk memberikan jeda sebelum retry:

public function backoff(): array
{
    return [10, 30, 60];
}

Sedangkan timeout dapat diatur sesuai kebutuhan:

public $timeout = 120;

Konsep yang paling penting adalah membedakan:

Delay
→ Menunda Job sebelum diproses

Retry
→ Mencoba kembali Job yang gagal

Backoff
→ Menentukan jeda sebelum retry

Timeout
→ Membatasi durasi satu attempt

Jangan lupa bahwa retry bukan berarti Job otomatis aman dijalankan berkali-kali. Untuk pekerjaan seperti payment, order, invoice, dan integrasi API, Job sebaiknya dirancang idempotent agar retry tidak menyebabkan efek samping ganda.

Dengan kombinasi Queue + Delay + Retry + Backoff + Monitoring, background processing Laravel menjadi jauh lebih tahan terhadap error sementara dan gangguan service eksternal.