September 27, 2026
Laravel Queue.
Menjalankan pekerjaan berat di background dengan Laravel Queue: job, driver, worker, retry & backoff, failed jobs, batching, Horizon, dan Supervisor untuk produksi.
Mengirim email, memproses gambar, memanggil API pihak ketiga, atau membuat laporan PDF tidak perlu membuat user menunggu. Dengan queue, request cukup “menitipkan” pekerjaan (job), lalu proses terpisah (worker) mengerjakannya di background. Catatan ini memakai sintaks Laravel 13.
1. Konsep
Request HTTP ──dispatch()──▶ [ Queue: database / redis / sqs ] ──▶ Worker (queue:work) ──▶ handle()
│ │
└── langsung response ke user └── retry / failed_jobs
| Istilah | Arti |
|---|---|
| Job | Class berisi pekerjaan (handle()) |
| Connection | Backend penyimpan antrean (database, redis, sqs, sync) |
| Queue | Nama antrean di dalam satu connection (default, emails, high) |
| Worker | Proses PHP jangka panjang yang mengambil & menjalankan job |
2. Driver
Set di .env:
QUEUE_CONNECTION=database
| Driver | Kelebihan | Catatan |
|---|---|---|
sync | Langsung dijalankan (tanpa antrean) | Untuk testing/debug saja |
database | Tanpa dependensi tambahan, default di project baru | Cukup untuk trafik kecil–menengah |
redis | Cepat, mendukung Horizon | Butuh Redis |
sqs | Managed oleh AWS, skalabel | Ada batas delay 15 menit |
null | Membuang job | Menonaktifkan queue |
Project Laravel baru sudah menyertakan migrasi
tabel jobs. Jika belum ada:
php artisan make:queue-table
php artisan migrate
3. Membuat & Dispatch Job
php artisan make:job SendInvoiceEmail
<?php
namespace App\Jobs;
use App\Mail\InvoiceMail;
use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;
class SendInvoiceEmail implements ShouldQueue
{
use Queueable;
public function __construct(public Order $order) {}
public function handle(): void
{
Mail::to($this->order->user)->send(new InvoiceMail($this->order));
}
}
Model yang dikirim ke constructor diserialisasi hanya ID-nya, lalu di-query ulang saat job dijalankan — jadi datanya selalu segar dan payload kecil.
Dispatch:
SendInvoiceEmail::dispatch($order);
SendInvoiceEmail::dispatch($order)
->onQueue('emails')
->delay(now()->addMinutes(5));
SendInvoiceEmail::dispatchIf($order->isPaid(), $order);
SendInvoiceEmail::dispatchSync($order); // jalankan sekarang, tanpa antrean
// Tunggu transaksi DB commit dulu, supaya job tidak membaca data yang belum tersimpan
SendInvoiceEmail::dispatch($order)->afterCommit();
Laravel 13 menambahkan queue routing terpusat, jadi job class tidak perlu tahu queue-nya:
// AppServiceProvider::boot()
use Illuminate\Support\Facades\Queue;
Queue::route(SendInvoiceEmail::class, connection: 'redis', queue: 'emails');
Mailable, Notification, dan Event Listener juga bisa di-queue dengan implements ShouldQueue — tidak harus selalu membuat Job sendiri.
4. Menjalankan Worker
php artisan queue:work # connection default, queue "default"
php artisan queue:work redis --queue=high,default,emails # urutan = prioritas
php artisan queue:work --tries=3 --timeout=90 --sleep=3
php artisan queue:work --max-jobs=500 --max-time=3600 # restart berkala (cegah memory leak)
php artisan queue:listen # reload kode tiap job; lambat, untuk dev
Worker adalah proses jangka panjang yang memuat kode sekali di awal. Setelah deploy kode baru, worker lama masih menjalankan kode lama, jadi selalu jalankan:
php artisan queue:restart
Di development, composer run dev pada project baru sudah menjalankan server, Vite, log, dan queue:listen sekaligus.
5. Retry, Backoff & Timeout
Laravel 13 bisa memakai attribute atau property klasik:
use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Tries;
#[Tries(5)]
#[Timeout(120)]
#[Backoff(delay: 10, maxDelay: 60, multiplier: 2)]
class SyncToPaymentGateway implements ShouldQueue
{
use Queueable;
// ...
}
// Cara klasik
class SyncToPaymentGateway implements ShouldQueue
{
public $tries = 5;
public $timeout = 120;
public $backoff = [10, 30, 60]; // jeda per percobaan (detik)
// atau batasi berdasarkan waktu, bukan jumlah percobaan
public function retryUntil(): \DateTime
{
return now()->addMinutes(30);
}
}
| Pengaturan | Fungsi |
|---|---|
tries | Jumlah percobaan maksimum sebelum job dianggap gagal |
backoff | Jeda sebelum retry — hindari membombardir API yang sedang down |
timeout | Batas waktu satu eksekusi; worker membunuh job yang melewati ini |
maxExceptions | Gagal lebih cepat jika exception (bukan release manual) terlalu banyak |
Aturan penting: timeout harus lebih kecil dari retry_after di config/queue.php. Jika tidak, job yang masih berjalan dianggap hilang dan diambil worker lain → job dieksekusi dua kali.
6. Failed Jobs
Job yang habis percobaannya masuk ke tabel failed_jobs.
use Throwable;
public function failed(?Throwable $exception): void
{
// beri tahu admin, ubah status order, dsb.
}
php artisan queue:failed # daftar
php artisan queue:retry all # coba ulang semua
php artisan queue:retry 5f3c... # coba ulang satu UUID
php artisan queue:forget 5f3c...
php artisan queue:flush # hapus semua failed job
php artisan queue:prune-failed --hours=48
7. Batching & Chaining
Chain: job dijalankan berurutan; jika satu gagal, sisanya berhenti.
use Illuminate\Support\Facades\Bus;
Bus::chain([
new GenerateInvoicePdf($order),
new SendInvoiceEmail($order),
new MarkInvoiceSent($order),
])->dispatch();
Batch: banyak job paralel dengan callback ketika selesai. Butuh tabel batch dan trait Batchable.
php artisan make:queue-batches-table
php artisan migrate
use Illuminate\Bus\Batch;
use Illuminate\Bus\Batchable;
use Throwable;
class ImportCsvChunk implements ShouldQueue
{
use Batchable, Queueable;
public function handle(): void
{
if ($this->batch()?->cancelled()) {
return;
}
// proses potongan CSV...
}
}
$batch = Bus::batch($chunks->map(fn ($rows) => new ImportCsvChunk($rows)))
->then(fn (Batch $batch) => logger('Import selesai'))
->catch(fn (Batch $batch, Throwable $e) => logger()->error($e->getMessage()))
->finally(fn (Batch $batch) => /* bersihkan file */ null)
->name('Import produk')
->dispatch();
// progres untuk ditampilkan di UI
Bus::findBatch($batch->id)->progress(); // 0–100
8. Job Middleware & Unique Job
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Queue\Middleware\WithoutOverlapping;
class RebuildSearchIndex implements ShouldQueue, ShouldBeUnique
{
use Queueable;
public function __construct(public Product $product) {}
public function uniqueId(): string
{
return (string) $this->product->id; // hanya satu job per produk di antrean
}
public function middleware(): array
{
return [new WithoutOverlapping($this->product->id)];
}
}
9. Horizon (untuk Redis)
Laravel Horizon adalah dashboard + manajer worker untuk queue berbasis Redis: throughput, runtime, failed job, dan auto-balancing jumlah worker per queue, dikonfigurasi lewat kode (config/horizon.php).
composer require laravel/horizon
php artisan horizon:install
php artisan horizon # menggantikan queue:work
# dashboard di /horizon (atur akses lewat gate viewHorizon)
php artisan horizon:terminate # setelah deploy
10. Produksi dengan Supervisor
Worker harus dijaga agar selalu hidup dan di-restart jika mati. Di server Linux, gunakan Supervisor:
sudo apt install supervisor
/etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"
sudo supervisorctl status
Untuk Horizon, command cukup php /var/www/app/artisan horizon dengan numprocs=1. Di Docker, jalankan worker sebagai service terpisah (lihat Docker Compose untuk Dev Environment
). stopwaitsecs harus lebih lama dari job terlama agar job tidak terpotong saat restart.
11. Pitfalls
- Lupa
queue:restartsetelah deploy → worker masih pakai kode lama. timeout≥retry_after→ job dobel.- Job tidak idempoten → retry bisa mengirim email dua kali atau menagih dua kali. Simpan status/flag sebelum aksi yang punya efek samping.
- Dispatch di dalam transaksi DB → worker membaca data yang belum di-commit. Pakai
afterCommit()(atauafter_commit => truedi config). - Mengirim objek besar/closure ke constructor → payload membengkak. Kirim model atau ID saja.
QUEUE_CONNECTION=syncterbawa ke produksi → semua job berjalan di dalam request, lambat.- Worker di Supervisor jalan sebagai
root→ file log/cache milik root, web server tidak bisa menulis. Setuser=www-data.
Queue dan retry otomatis menjadi tulang punggung provisioning di Web Host Manager . Catatan terkait: Eloquent ORM , Cron Basics .

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.