Antrean bisa menampung sejuta job, tetapi tanpa worker tidak ada satu pun yang dikerjakan. Worker adalah proses PHP CLI yang berjalan lama (long-running), berputar dalam loop mengambil job dan mengeksekusinya — konsep yang berbeda dari request HTTP yang lahir dan mati dalam hitungan detik. Perbedaan ini membawa konsekuensi operasional sendiri: worker harus dijalankan terus-menerus, diberi batas memori, dan di-restart dengan cara yang benar saat deployment. Pada chapter ini kita menjalankan worker sungguhan, membedah lifecycle-nya, mengatur prioritas antrean, lalu menutup dengan mekanisme restart yang elegan.

Menjalankan Worker #

Perintah kerjanya sederhana:

bash
php artisan queue:work

Worker memakai connection dari QUEUE_CONNECTION — database di sandbox kita — dan mengawasi antrean default. Untuk melihat kerjanya tanpa proses yang menggantung selamanya, flag --stop-when-empty membuat worker keluar begitu antrean kosong. Kita dispatch satu job lalu menjalankannya:

bash
php artisan queue:work --stop-when-empty

Output yang Dihasilkan:

text
2026-10-13 20:34:40 App\Jobs\SendWelcomeEmail ...................... RUNNING
2026-10-13 20:34:40 App\Jobs\SendWelcomeEmail ................. 18.38ms DONE
2026-10-13 20:34:43 Worker STOPPED Queue empty

Setiap baris memuat timestamp, nama class job, status (RUNNING, DONE, atau FAIL), dan durasi pengerjaan — 18.38 milidetik untuk email kita. Baris terakhir mencatat alasan worker berhenti: antrean kosong. Tanpa --stop-when-empty, worker justru diam menunggu; saat idle ia tidak mencetak apa pun, hanya bangun tiap beberapa detik memeriksa antrean, dan terus hidup sampai kita menghentikannya dengan Ctrl+C.

Lifecycle Worker #

Sejak dijalankan hingga berhenti, worker menjalani pola yang tetap. Proses PHP diboot sekali di awal — framework dimuat, provider terdaftar, event listener terpasang — lalu masuk loop yang tidak membaca ulang file kode:

  1. Pop — worker mem-query antrean untuk job yang tersedia; bila kosong, worker sleep selama --sleep detik (default 3) lalu mencoba lagi.
  2. Execute — job di-reserve (kolom reserved_at diisi), payload di-unserialize, lalu handle() dipanggil.
  3. Resolve — job sukses dihapus dari antrean; job gagal dilepas kembali dengan jeda backoff, atau dipindahkan ke failed_jobs bila percobaannya habis.

Framework yang hanya diboot sekali inilah keunggulan worker daemon: hemat waktu bootstrap untuk tiap job. Konsekuensinya juga nyata — memori tidak dilepas otomatis antar job, dan kode yang berubah setelah deployment tidak terbaca ulang sampai worker di-restart.

Prioritas Antrean #

Flag --queue menentukan antrean mana yang diawasi worker, berurutan dari yang paling diprioritaskan. Kita uji dengan dua job: PublishPost masuk antrean default duluan, SendWelcomeEmail masuk antrean emails belakangan:

php
use App\Models\User;
use App\Models\Post;
use App\Jobs\SendWelcomeEmail;
use App\Jobs\PublishPost;
use Illuminate\Support\Facades\DB;

$budi = User::where('email', '[email protected]')->firstOrFail();
$post = Post::firstOrCreate(['title' => 'Belajar Queue Laravel'], ['user_id' => $budi->id]);

// job default masuk duluan...
PublishPost::dispatch($post);
// ...job emails masuk belakangan
SendWelcomeEmail::dispatch($budi)->onQueue('emails');

foreach (DB::table('jobs')->orderBy('id')->get(['id', 'queue']) as $row) {
    echo "job_id={$row->id} queue={$row->queue}\n";
}

Output yang Dihasilkan:

text
job_id=1 queue=default
job_id=2 queue=emails

Lalu worker dijalankan dengan php artisan queue:work --stop-when-empty --queue=emails,default:

text
2026-10-13 20:37:57 App\Jobs\SendWelcomeEmail ...................... RUNNING
2026-10-13 20:37:57 App\Jobs\SendWelcomeEmail ................. 19.61ms DONE
2026-10-13 20:37:57 App\Jobs\PublishPost ........................... RUNNING
2026-10-13 20:37:57 App\Jobs\PublishPost ....................... 1.51ms DONE
2026-10-13 20:38:00 Worker STOPPED Queue empty

Urutan terbalik dari urutan masuk: job di antrean emails dikerjakan lebih dulu meski baru masuk belakangan, karena emails ditulis lebih dulu di flag --queue. Log Laravel mengonfirmasi To: [email protected] terisi sebelum Post diproses: Belajar Queue Laravel. Susunan ini juga cara umum melindungi antrean penting: --queue=pembayaran,default memastikan job pembayaran tidak mengantre di belakang pekerjaan berat.

Flag Penting queue:work #

FlagDefaultFungsi
--queue=defaultAntrean yang diawasi, kiri lebih diprioritaskan
--tries=1Jumlah percobaan sebelum job dinyatakan gagal
--timeout=60Batas detik eksekusi satu job
--sleep=3Jeda tidur saat antrean kosong
--memory=128Batas memori MB; worker keluar bila terlampaui
--max-time=0Batas umur worker dalam detik
--once–Proses satu job lalu keluar
--stop-when-empty–Keluar saat antrean kosong

Flag --tries dan --timeout di command line menjadi default untuk semua job yang diproses worker itu, tetapi properti job $tries, $backoff, dan $timeout di dalam class job selalu menang atas flag — pembahasan lengkapnya ada di chapter Job Retries . Kombinasi --max-time berguna untuk pola cron yang akan kita lihat di akhir chapter, sementara --memory adalah katup pengaman kebocoran memori khas proses long-running: begitu terlampaui, worker berhenti dengan anggun dan menyerahkan pada pengawasnya untuk menghidupkan ulang.

Restart yang Elegan #

Setiap deployment kode baru, semua worker wajib di-restart agar kode terbaru terbaca. Mematikan worker secara paksa di tengah job berisiko memotong pekerjaan setengah jalan. Laravel menyelesaikannya lewat sinyal lewat cache:

bash
php artisan queue:restart

Output yang Dihasilkan:

text
INFO  Broadcasting queue restart signal.

Perintah ini tidak menyentuh worker secara langsung — ia hanya menulis timestamp ke cache dengan kunci illuminate:queue:restart (pada cache store database, tersimpan sebagai baris dengan key laravel-cache-illuminate:queue:restart). Setiap worker mengingat timestamp saat ia lahir; di setiap putaran loop yang tidak sedang memegang job, worker membandingkan umurnya dengan timestamp di cache. Bila timestamp lebih baru, worker berhenti dengan rapi:

text
2026-10-13 20:26:00 Worker STOPPED Received restart signal

Job yang sedang dikerjakan tetap dituntaskan sampai selesai; hanya setelah itu worker keluar. Itulah sebabnya perintah ini disebut graceful restart — kode baru efektif paling lama setelah job berjalan selesai, tanpa ada pekerjaan yang terpotong.

Banyak Worker dan Pola Pengawasan #

Satu worker memproses job satu per satu; antrean menumpuk bila job datang lebih cepat dari dikerjakan. Solusinya menjalankan beberapa proses worker sekaligus — dua worker yang mengawasi antrean sama akan membagi job di antara mereka. Di server produksi, pengawasan proses ini diserahkan pada Supervisor:

ini
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/example-com/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopwaitsecs=3600
user=example-com
numprocs=2

Konfigurasi di atas (blok tampilan — membutuhkan Supervisor terpasang di server) menjalankan dua worker (numprocs=2) yang otomatis dihidupkan ulang bila mati; --max-time=3600 memutar ulang worker tiap jam sebagai pembersihan memori berkala. Alternatif tanpa Supervisor adalah cron per menit:

bash
* * * * * cd /home/example-com && php artisan queue:work --stop-when-empty --max-time=55 >> /dev/null 2>&1

Pola cron ini memakai flag yang sudah kita kenal: worker hidup maksimal 55 detik, menghabiskan antrean, lalu mati — biaya bootstrap per menit ditukar dengan proses yang selalu segar. Untuk antrean yang sudah sesat isinya, queue:clear membuang seluruh isinya tanpa mengerjakan:

bash
php artisan queue:clear database --force

Output yang Dihasilkan:

text
INFO  Cleared 2 jobs from the [default] queue.

Penjelasan mendalam flag worker bisa dirujuk di dokumentasi Queues . Worker kita kini hidup dan bekerja — tinggal satu pertanyaan yang belum terjawab: apa yang terjadi bila job yang ia kerjakan terus-menerus gagal? Kebijakan percobaan ulangnya kita susun di chapter berikutnya: Job Retries .