Bayangkan user baru saja menekan tombol “Daftar” di aplikasi kita. Di balik layar, controller harus menyimpan data user lalu mengirim email selamat datang. Mengirim email bukan pekerjaan instan: merender template, menyusun MIME, menunggu SMTP menjawab — di server yang sibuk bisa makan waktu beberapa detik. Jika kode pengiriman email kita tulis langsung di dalam request, user menatap layar loading selama proses itu, padahal satu-satunya hal yang user butuhkan adalah konfirmasi “pendaftaran diterima”. Queue hadir untuk memutus keterikatan ini: request cukup mencatat “ada pekerjaan yang harus dikerjakan”, lalu menutup diri dengan cepat, dan pekerjaan beratnya dikerjakan di latar belakang oleh proses lain.
Request yang Memblokir Response #
Mari kita ukur dulu sakitnya. Runner berikut menyimulasikan pengiriman email lambat dua detik dengan sleep(2), lalu membandingkannya dengan memasukkan pekerjaan yang sama ke antrean:
use App\Models\User;
use App\Jobs\SendWelcomeEmail;
use Illuminate\Support\Facades\DB;
DB::table('jobs')->delete();
$user = User::where('email', '[email protected]')->firstOrFail();
// skenario A: kirim email sinkron di dalam request
$mulai = microtime(true);
sleep(2); // simulasi: render template + SMTP lambat menjawab
$durasiSinkron = round(microtime(true) - $mulai, 2);
// skenario B: tunda pekerjaan ke antrean
$mulai = microtime(true);
SendWelcomeEmail::dispatch($user);
$durasiDispatch = round((microtime(true) - $mulai) * 1000);
echo "durasi_kirim_sinkron: {$durasiSinkron} detik\n";
echo "durasi_dispatch: {$durasiDispatch} ms\n";
echo "jobs_di_antrean: " . DB::table('jobs')->count() . "\n";Output yang Dihasilkan:
durasi_kirim_sinkron: 2 detik
durasi_dispatch: 9 ms
jobs_di_antrean: 1Perbedaannya mencolok: skenario A menahan request selama dua detik penuh, sedangkan dispatch() di skenario B selesai dalam hitungan milidetik. Yang dilakukan dispatch() hanyalah menyimpan satu baris data ke tabel jobs — pekerjaan beratnya belum dikerjakan sama sekali. Konsekuensinya, request HTTP bisa segera mengembalikan response, dan pekerjaan email dikerjakan sesudahnya oleh proses terpisah yang bernama worker.
Menunda Pekerjaan ke Latar Belakang #
Idenya sederhana: daripada mengerjakan semuanya di dalam satu request, kita pecah menjadi dua fase. Fase pertama (di dalam request) hanya menerima pekerjaan dan mencatatnya ke penyimpanan antrean. Fase kedua (di luar request) membaca catatan itu lalu mengerjakannya. User mendapat response cepat, sementara email, pemrosesan gambar, atau panggilan API lambat tetap tuntas — hanya waktunya bergeser beberapa detik ke belakang.
Mekanisme ini menjawab tiga masalah sekaligus. Pertama, response time tetap rendah walau pekerjaannya mahal. Kedua, kegagalan sementara (misalnya API eksternal sedang down) tidak merusak request — pekerjaan bisa dicoba ulang oleh worker. Ketiga, beban bisa dikendalikan: satu worker yang sibuk tidak memperlambat user, ia hanya menunda pekerjaan di antreannya.
Anatomi Sebuah Queue #
Empat komponen membentuk lingkaran penuh sebuah queue di Laravel:
- Dispatch — kode kita memanggil
SendWelcomeEmail::dispatch($user). - Storage — job terserialisasi menjadi payload dan disimpan (misalnya baris di tabel
jobs). - Worker — proses
php artisan queue:workmengambil job dari storage lalu mengeksekusinya. - Result — hasil pekerjaan terlihat di tempat tujuannya: email terkirim, baris database berubah, log terisi.
Diagram di atas menegaskan pemisahan yang paling penting: controller dan worker adalah dua proses yang berbeda, terhubung hanya oleh penyimpanan antrean. Karena itu payload job harus bisa disimpan — data mentah, primitive, dan model Eloquent aman digunakan, dan cara Laravel menanganinya kita bahas tuntas di chapter Membuat Job .
FIFO: Masuk Duluan, Keluar Duluan #
Queue database Laravel bersifat FIFO (first in, first out) — job yang didispatch lebih dulu dikerjakan lebih dulu. Mari kita buktikan dengan tiga job yang masuk berturut-turut:
SendWelcomeEmail::dispatch($budi);
PublishPost::dispatch($post);
SendWelcomeEmail::dispatch($siti);
foreach (DB::table('jobs')->orderBy('id')->get(['id', 'queue']) as $row) {
echo "job_id={$row->id} queue={$row->queue}\n";
}Output yang Dihasilkan:
job_id=1 queue=default
job_id=2 queue=default
job_id=3 queue=defaultKetiga job menempati baris tabel berurutan. Setelah worker dijalankan dengan php artisan queue:work --stop-when-empty, urutan pengerjaannya persis sama dengan urutan masuknya:
2026-10-13 20:37:47 App\Jobs\SendWelcomeEmail ...................... RUNNING
2026-10-13 20:37:47 App\Jobs\SendWelcomeEmail ................. 19.42ms DONE
2026-10-13 20:37:47 App\Jobs\PublishPost ........................... RUNNING
2026-10-13 20:37:47 App\Jobs\PublishPost ....................... 1.62ms DONE
2026-10-13 20:37:47 App\Jobs\SendWelcomeEmail ...................... RUNNING
2026-10-13 20:37:47 App\Jobs\SendWelcomeEmail .................. 1.50ms DONE
2026-10-13 20:37:50 Worker STOPPED Queue emptyLog Laravel memperkuat bukti: email untuk [email protected] terkirim, disusul Post diproses: Belajar Queue Laravel, terakhir email untuk [email protected] — persis urutan dispatch. Satu pengecualian yang perlu dicatat: job yang di-delay() ke masa depan dilewati sampai waktunya tiba, dan antrean bernama tertentu bisa diprioritaskan lewat urutan flag --queue — keduanya kita bahas di chapter Dispatching Jobs
dan Queue Workers
.
Kapan Kita Tidak Perlu Queue #
Queue bukan obat untuk semua pekerjaan. Setiap job membayar biaya: serialisasi payload, penulisan ke storage, dan proses worker terpisah. Untuk pekerjaan ringan, biaya itu lebih besar daripada manfaatnya. Panduan praktisnya:
| Kriteria pekerjaan | Penanganan yang tepat |
|---|---|
| Selesai di bawah ~100 ms (hitung total, simpan notifikasi) | Eksekusi langsung di request |
| 100 ms sampai beberapa detik (kirim email, resize gambar) | Queue |
| Berat namun terjadwal (laporan mingguan, pembersihan data) | Task Scheduling + Queue |
| Butuh urutan langkah antar job | Job chains |
Ekosistem Queue Laravel #
Sepanjang section ini kita akan menyusun fondasinya bertahap: mengonfigurasi driver, membuat class job, mendispatch dengan delay dan antrean bernama, menjalankan worker, lalu mengatur percobaan ulang. Setelah fondasi itu berdiri, material lanjutan seperti failed jobs, batching, chaining, dan rate limiting menunggu di paruh kedua section. Ketika nanti antrean kita tumbuh besar dengan driver Redis, package Horizon resmi Laravel layak dipelajari untuk memantau throughput, waktu tunggu, dan kegagalan worker lewat dashboard. Rujukan resmi lengkapnya selalu ada di dokumentasi Queues .
Sebelum bisa mendispatch job pertama, kita perlu memilih di mana antrean itu disimpan — database, Redis, atau yang lainnya. Peta lengkap keputusan itu ada di chapter berikutnya: Queue Configuration .