Di lokal, job antrean berjalan senyap karena driver sync mengeksekusinya seketika. Di production dengan driver database atau redis, job menunggu diambil oleh proses queue:work yang harus hidup terus-menerus — dan di sinilah masalah klasiknya: worker yang dijalankan lewat SSH mati begitu koneksi terminal ditutup. Chapter ini membahas cara mengawasi worker dengan supervisor, mengapa worker perlu dirotasi secara berkala, dan bagaimana queue:restart menghentikan worker dengan anggun saat deploy — dengan mekanisme yang kita buktikan nyata lewat cache.

Keterbatasan Menjalankan Worker Manual #

Sebelum solusinya, mari lihat pekerjaan worker secara nyata. Kita siapkan job sederhana yang mencatat log, lalu memasukkannya ke antrean database:

php
namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class KirimPemberitahuan implements ShouldQueue
{
    use Queueable;

    public function __construct(public string $nama)
    {
    }

    public function handle(): void
    {
        Log::info("Pemberitahuan untuk {$this->nama} terkirim.");
    }
}

Dua job dikirim untuk Budi dan Siti, lalu worker menguras antrean:

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

Output yang Dihasilkan:

text
  2026-10-13 22:19:26 App\Jobs\KirimPemberitahuan .................... RUNNING
  2026-10-13 22:19:26 App\Jobs\KirimPemberitahuan ................ 4.97ms DONE
  2026-10-13 22:19:26 App\Jobs\KirimPemberitahuan .................... RUNNING
  2026-10-13 22:19:26 App\Jobs\KirimPemberitahuan ................ 0.73ms DONE
  2026-10-13 22:19:29 Worker STOPPED Queue empty

Log membuktikan keduanya benar-benar dieksekusi: Pemberitahuan untuk Budi terkirim. dan Pemberitahuan untuk Siti terkirim.. Perintah di atas memang sengaja berhenti saat antrean kosong — padahal di production justru sebaliknya yang dibutuhkan: worker harus hidup selamanya menunggu job baru. Menjalankannya dengan nohup php artisan queue:work & lalu menutup SSH memang “bekerja”, tetapi tiga hal tidak terurus: tidak ada yang menghidupkan ulang worker saat crash atau saat server reboot, tidak ada pengelolaan beberapa worker paralel, dan output tercampur di satu berkas tanpa rotasi. Supervisor menuntaskan ketiganya.

Supervisor Menjaga Worker Tetap Hidup #

Supervisor adalah process control system yang menjalankan queue:work sebagai daemon dan bangkit otomatis saat mati. Berikut contoh tampilan konfigurasinya di /etc/supervisor/conf.d/tokobudi-worker.conf:

ini
[program:tokobudi-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/tokobudi/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/tokobudi/storage/logs/worker.log
stopwaitsecs=3600

Arahan kuncinya: autostart=true menghidupkan worker saat server boot; autorestart=true membangkitkannya lagi setiap kali proses mati; numprocs=2 menjalankan dua worker paralel dengan nama tokobudi-worker_00 dan _01; stdout_logfile menampung output ke satu berkas log; dan stopwaitsecs=3600 memberi job yang sedang berjalan waktu hingga satu jam sebelum dipaksa dimatikan. Setelah konfigurasi aktif, siklus hidupnya dikelola lewat supervisorctl — contoh tampilan:

bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status
sudo supervisorctl restart "tokobudi-worker:*"

Rotasi Worker dan –max-jobs #

Worker queue:work adalah proses PHP yang panjang umur, dan sejarah telah membuktikan kode pihak ketiga pelan-pelan membocorkan memori — memory leak pada paket yang buruk membuat worker membengkak hingga ratusan MB lalu mati oleh OOM killer. Dua flag merawatnya. --max-time=3600 mematikan worker setelah satu jam — supervisor langsung menggantinya dengan proses segar, seperti tercantum pada blok konfigurasi di atas. --max-jobs membatasinya per jumlah job; kita buktikan dengan mengantrekan dua job lalu menjalankan worker yang berhenti setelah satu:

bash
php artisan queue:work --max-jobs=1

Output yang Dihasilkan:

text
  2026-10-13 22:19:43 App\Jobs\KirimPemberitahuan .................... RUNNING
  2026-10-13 22:19:43 App\Jobs\KirimPemberitahuan ................ 4.92ms DONE
  2026-10-13 22:19:43 Worker STOPPED Maximum jobs exceeded

Satu job selesai, worker keluar dengan sopan, dan job kedua tinggal menunggu proses pengganti. Kombinasi keduanya memastikan memori worker selalu muda tanpa intervensi manual.

queue:restart Saat Deploy #

Kode baru yang dideploy tidak otomatis dipakai worker lama — proses PHP sudah memuat kelas lama ke memori. Momen deploy karenanya harus disertai queue:restart, dan menariknya perintah ini tidak menyentuh worker sama sekali; perintah hanya menulis sinyal ke cache store:

bash
php artisan queue:restart

Output yang Dihasilkan:

text
   INFO  Broadcasting queue restart signal.  

Sinyalnya tersimpan sebagai satu baris di tabel cache — kita baca isinya langsung dari database:

text
laravel-cache-illuminate:queue:restart => i:1791929974;

Nilai i:1791929974 adalah integer unix timestamp (2026-10-13 22:19:34 UTC) waktu sinyal dibuat. Setiap worker mengingat kapan dirinya start; saat loop antrean menemukan timestamp restart yang lebih baru dari start-nya, worker menyelesaikan job yang sedang diproses lalu keluar dengan bersih. Inilah yang membuatnya graceful: job yang sedang berjalan tidak terputus. Supervisor kemudian membangkitkan worker baru yang memuat kode hasil deploy. Potongan skrip deploy tipikal (contoh tampilan):

bash
php artisan migrate --force
php artisan config:cache
php artisan queue:restart

Horizon dan Job yang Selamat #

Untuk driver redis, paket resmi Horizon menyediakan dasbor pemantauan worker, throughput, dan job gagal — sekaligus mengelola prosesnya sendiri lewat php artisan horizon, menggantikan konfigurasi supervisor manual di atas. Apapun pengawasnya, satu sifat tetap: job tertunda (delay), percobaan ulang, dan failed jobs tersimpan di database/Redis, bukan di memori worker — restart apapun tidak memusnahkan antrean, sebagaimana dibahas di Queue Workers . Hanya job yang sedang diproses saat restart yang perlu menunggu satu siklus — itupun diselesaikan dulu oleh mekanisme graceful.

Worker kini hidup di bawah pengawasan. Penghuni proses selanjutnya adalah tugas terjadwal kita — cukup satu baris cron untuk seluruh jadwal aplikasi, dibahas di Scheduled Tasks di Production .