Queue & job
Antrean mengubah "pengguna menunggu 8 detik" jadi "pengguna menunggu 80 milidetik dan pekerjaannya selesai belakangan". Yang perlu dipelajari adalah kegagalan, pengulangan, dan idempotensi.
Intisari
- Job adalah kelas dengan
handle().Job::dispatch($data)menaruhnya di antrean. - Driver:
sync(langsung, untuk lokal),database,redis,sqs. Produksi di AWS memakai Redis atau SQS. - Job akan dijalankan lebih dari sekali. Tulis supaya aman diulang โ atau pakai
ShouldBeUnique. - Selalu sediakan
tries,backoff,timeout, dan tabel failed jobs. - Laravel 13 menambahkan
Queue::route()dan atribut#[Tries],#[Backoff],#[Timeout].
Sebuah job
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\Timeout;
#[Tries(5)]
#[Backoff([10, 60, 300])]
#[Timeout(120)]
class BuatInvoicePdf implements ShouldQueue
{
use Queueable;
public function __construct(public readonly int $pesananId) {}
public function handle(PembuatPdf $pdf): void // dependensi diinjeksi container
{
$pesanan = Pesanan::findOrFail($this->pesananId);
// Idempoten: kalau sudah pernah dibuat, berhenti โ job bisa jalan dua kali.
if ($pesanan->invoice_path) {
return;
}
$path = $pdf->buat($pesanan);
$pesanan->update(['invoice_path' => $path]);
}
public function failed(Throwable $e): void
{
Log::error('invoice gagal', ['pesanan' => $this->pesananId, 'sebab' => $e->getMessage()]);
}
}
BuatInvoicePdf::dispatch($pesanan->id);
BuatInvoicePdf::dispatch($id)->onQueue('berat')->delay(now()->addMinutes(5));
BuatInvoicePdf::dispatch($id)->afterCommit(); // hanya kalau transaksi berhasil
Kirim ID, bukan objek besar. Job diserialisasi jadi teks di dalam antrean. Trait
SerializesModels memang menyimpan ID saja, tapi properti lain โ array besar, koleksi, closure โ
ikut diserialisasi utuh. Payload SQS dibatasi 256 KB, dan payload gemuk memperlambat semuanya. Kirim ID, ambil
datanya di dalam handle().
Idempotensi: aturan yang tidak bisa ditawar
Job akan dijalankan lebih dari sekali. Bukan karena kodemu buruk, melainkan karena: worker mati di tengah jalan setelah pekerjaan selesai tapi sebelum sempat menghapus pesannya; SQS Standard memang menjamin at-least-once; percobaan ulang terjadi setelah timeout padahal yang pertama masih berjalan.
| Pola | Bahaya kalau berjalan dua kali | Perbaikan |
|---|---|---|
| Kirim email | Pengguna menerima dua kali | Catat penanda kirim, cek dulu |
| Tagih kartu | Ditagih dua kali | Kunci idempotensi di sisi gerbang pembayaran |
increment('poin', 10) | Poin dobel | Simpan sebagai baris transaksi, bukan penambahan |
| Buat PDF | Boros, tapi tidak merusak | Cek keberadaan berkasnya lebih dulu |
// Satu job aktif per kunci โ mencegah tumpang tindih
class SinkronKatalog implements ShouldQueue, ShouldBeUnique
{
public $uniqueFor = 3600;
public function uniqueId(): string
{
return 'katalog:'.$this->tokoId;
}
}
Memilih driver
| Driver | Kapan | Perlu diingat |
|---|---|---|
sync | Lokal & tes | Bukan antrean โ berjalan seketika di proses yang sama |
database | Aplikasi kecil, satu server | Setiap worker mem-polling database |
redis | Default yang baik di AWS | Latensi rendah, dan mendukung Horizon (Fase 7) |
sqs | Skala besar, lintas layanan | Terkelola penuh; batas payload 256 KB, tanpa Horizon |
// Laravel 13: routing terpusat, tidak perlu diulang di tiap dispatch
Queue::route(BuatInvoicePdf::class, connection: 'redis', queue: 'berat');
Queue::route(KirimEmail::class, connection: 'sqs', queue: 'notifikasi');
Menjalankan worker
# produksi: proses sekali jalan lalu keluar, dijaga supervisor/ECS
php artisan queue:work redis --queue=penting,default --tries=3 --max-time=3600
# lokal: memuat ulang kode tiap job
php artisan queue:listen
| Opsi | Gunanya |
|---|---|
--queue=penting,default | Prioritas: penting dihabiskan dulu |
--tries=3 | Batas percobaan sebelum masuk failed jobs |
--max-time=3600 | Keluar setelah sejam โ mengatasi kebocoran memori |
--max-jobs=1000 | Keluar setelah sekian job; efek yang sama |
--timeout=60 | Bunuh job yang menggantung |
queue:work memuat kode ke memori dan tidak melihat perubahanmu. Setelah deploy, worker
wajib di-restart (php artisan queue:restart) atau task-nya diganti โ kalau tidak,
worker lama akan memproses job baru dengan kode lama. Di Fase 9, ini jadi satu poin eksplisit dalam urutan
deploy ECS.
Job yang gagal
php artisan queue:failed # daftar
php artisan queue:retry all # coba lagi semuanya
php artisan queue:retry 5 # satu job
php artisan queue:flush # buang
Tabel failed_jobs adalah jaring pengaman terakhir. Kalau ia tidak pernah dibaca, kegagalan berarti
pekerjaan yang hilang diam-diam. Pasang alarm atas jumlah barisnya โ di Fase 9 ini menjadi metrik CloudWatch.
Batch dan rantai
// Berurutan: berhenti kalau salah satu gagal
Bus::chain([
new TagihPembayaran($id),
new BuatInvoicePdf($id),
new KirimStruk($id),
])->dispatch();
// Paralel, dengan callback setelah semuanya selesai
Bus::batch($produk->map(fn ($p) => new SinkronKeMarketplace($p->id)))
->then(fn (Batch $b) => Log::info('sinkron selesai', ['total' => $b->totalJobs]))
->catch(fn (Batch $b, Throwable $e) => Log::error('sinkron gagal'))
->allowFailures()
->dispatch();
Latihan: buat job BuatInvoicePdf yang sengaja throw pada percobaan pertama
dan berhasil pada percobaan kedua. Jalankan dengan --tries=3, amati jeda backoff-nya,
lalu ubah supaya selalu gagal dan periksa isi tabel failed_jobs.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.