Worker

Menjalankan worker antrian, percobaan ulang, dan penanganan job yang gagal.

Daftar isi

Worker adalah proses yang mengambil job dari antrian dan menjalankannya, sehingga tanpa worker, job yang sudah dikirim ke antrian hanya menumpuk di media penyimpanan. Topik ini membahas cara menjalankan worker pada aplikasi catatan, mekanisme percobaan ulang ketika job melempar error, dan penanganan job yang akhirnya gagal melalui tabel failed_jobs. Setelah membacanya, job dapat dijalankan sekali jalan maupun sebagai daemon, batas percobaannya diatur, dan job yang gagal dapat diperiksa lalu diulang.

Menjalankan worker#

Worker dijalankan dengan perintah queue:work. Tanpa opsi tambahan, perintah ini berperilaku sebagai daemon, yaitu proses yang berjalan terus-menerus dan memantau antrian untuk mengambil job baru setiap kali tersedia. Berbeda dari perintah Artisan lain yang selesai dalam hitungan detik, worker akan berjalan terus sampai dihentikan dengan Ctrl+C, sehingga sesuai untuk dijalankan pada terminal tersendiri.

bash
# tidak-dijalankan
# menjalankan worker sebagai daemon (berjalan terus sampai dihentikan)
cd ~/aplikasi-catatan
php artisan queue:work

Perintah di atas tidak dieksekusi ulang pada contoh ini karena prosesnya tidak pernah selesai dengan sendirinya. Keluaran berikut diambil dari eksekusi nyata perintah php artisan queue:work --stop-when-empty, yang perilaku per-job-nya sama dengan daemon namun berhenti setelah antrian kosong.

text Hasil perintah queue:work --stop-when-empty
  2026-11-03 14:49:02 App\Jobs\EksporCatatan ......................... RUNNING
Catatan Pengingat arsip bulanan diekspor ke berkas CSV
  2026-11-03 14:49:02 App\Jobs\EksporCatatan ..................... 6.46ms DONE
  2026-11-03 14:49:05 Worker STOPPED Queue empty

Setiap job menghasilkan dua baris status, yaitu RUNNING ketika mulai diproses dan DONE ketika selesai, lengkap dengan waktu dan durasinya. Baris tengah berasal dari echo pada metode handle() job EksporCatatan. Dua opsi sering digunakan untuk menjalankan worker secara terkendali, yaitu --stop-when-empty yang berhenti ketika antrian kosong dan --once yang hanya memproses satu job. Contoh berikut menyiapkan dua catatan secara deterministik, mengirim dua job, lalu memprosesnya dengan --once, sehingga satu job masih tersisa di antrian.

bash
# mengirim dua job lalu memproses hanya satu dengan --once
cd ~/aplikasi-catatan
php artisan queue:clear --force > /dev/null
php artisan tinker --execute='
use App\Jobs\KirimPengingat;
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\Queue;
$kerja = Category::firstOrCreate(["nama" => "Kerja"]);
$pertama = Note::firstOrCreate(
    ["judul" => "Pengingat arsip bulanan"],
    ["isi" => "Menandai catatan yang perlu diarsipkan pada akhir bulan.", "category_id" => $kerja->id]
);
$kedua = Note::firstOrCreate(
    ["judul" => "Catatan rapat pekanan"],
    ["isi" => "Mencatat poin rapat tim setiap pekan.", "category_id" => $kerja->id]
);
Queue::push(new KirimPengingat($pertama));
Queue::push(new KirimPengingat($kedua));
echo "Ukuran antrian: " . Queue::size() . PHP_EOL;
'
php artisan queue:work --once --quiet
php artisan tinker --execute='echo "Sisa job: " . Illuminate\Support\Facades\Queue::size() . PHP_EOL;'
text Keluaran
Ukuran antrian: 2
Pengingat untuk catatan: Pengingat arsip bulanan
Sisa job: 1

Opsi --quiet meredam baris status RUNNING dan DONE sehingga hanya teks dari job yang tampak. Pada server produksi, worker biasanya dijalankan sebagai daemon dan diawasi proses supervisor, yaitu program penjaga proses, agar dijalankan kembali secara otomatis ketika berhenti. Opsi --once dan --stop-when-empty lebih sering digunakan pada pengujian dan proses terjadwal.

Percobaan ulang job yang error#

Kegagalan eksekusi job ditangani worker dengan mekanisme percobaan ulang. Apabila metode handle() melempar error, worker tidak langsung membuang job tersebut, melainkan mencatat percobaannya dan mengulang eksekusi sampai batas tertentu. Batasnya ditentukan oleh properti $tries pada kelas job, atau oleh opsi --tries pada perintah worker apabila properti itu tidak disetel. Menyetel nilai 0 berarti jumlah percobaannya tidak dibatasi.

php Contoh: batas percobaan dan jeda pada kelas job
// tidak-dijalankan
public int $tries = 3;

// jeda sebelum percobaan berikutnya, dalam detik
public array $backoff = [10, 60];

// batas waktu satu eksekusi job
public int $timeout = 30;

Array $backoff berarti jeda sepuluh detik sebelum percobaan kedua dan enam puluh detik sebelum percobaan berikutnya, sedangkan nilai tunggal berlaku untuk semua percobaan. Konfigurasi retry_after pada koneksi database di config/queue.php melengkapi mekanisme ini. Job yang dieksekusi worker melebihi nilai retry_after dianggap hilang dan dikembalikan ke antrian, sehingga nilainya harus lebih besar daripada durasi job terlama.

Kelas EksporCatatan yang digunakan contoh pada topik ini menerima satu model Note beserta format ekspornya, dan melempar error ketika formatnya bukan CSV. Kelas lengkapnya adalah sebagai berikut.

php EksporCatatan.php
// tidak-dijalankan
<?php

namespace App\Jobs;

use App\Models\Note;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use RuntimeException;

class EksporCatatan implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(public Note $note, public string $format = 'csv')
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        if ($this->format !== 'csv') {
            throw new RuntimeException("Format ekspor {$this->format} belum didukung");
        }

        echo "Catatan {$this->note->judul} diekspor ke berkas CSV" . PHP_EOL;
    }
}

Contoh berikut mengirim job dengan format xml yang tidak didukung, kemudian menjalankan worker dengan --tries=1 agar kegagalannya langsung tercatat.

bash
# mengirim job yang melempar error lalu memprosesnya dengan satu percobaan
cd ~/aplikasi-catatan
php artisan queue:clear --force > /dev/null
php artisan queue:flush > /dev/null
php artisan tinker --execute='
use App\Jobs\EksporCatatan;
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\Queue;
$kerja = Category::firstOrCreate(["nama" => "Kerja"]);
$catatan = Note::firstOrCreate(
    ["judul" => "Pengingat arsip bulanan"],
    ["isi" => "Menandai catatan yang perlu diarsipkan pada akhir bulan.", "category_id" => $kerja->id]
);
Queue::push(new EksporCatatan($catatan, "xml"));
echo "Ukuran antrian: " . Queue::size() . PHP_EOL;
'
php artisan queue:work --stop-when-empty --tries=1 --quiet
sqlite3 database/database.sqlite "select count(*) from failed_jobs;"
text Keluaran
Ukuran antrian: 1
1

Antrian kosong kembali setelah worker selesai, tetapi jumlah baris pada tabel failed_jobs bertambah menjadi satu. Job yang melampaui batas percobaannya tidak dihapus, melainkan dipindahkan dari tabel jobs ke tabel failed_jobs beserta pesan errornya, sehingga penyebab kegagalannya masih dapat diperiksa kemudian.

Melihat dan mengulang job yang gagal#

Daftar job yang gagal dilihat dengan perintah queue:failed, dan setiap barisnya memuat waktu kegagalan, identifier unik, koneksi beserta nama antrian, serta nama kelas job.

bash
# tidak-dijalankan
# melihat daftar job yang gagal
cd ~/aplikasi-catatan
php artisan queue:failed

Perintah tersebut tidak dieksekusi ulang pada contoh ini karena keluarannya memuat tanggal dan identifier yang berubah pada setiap percobaan. Keluaran aslinya pada keadaan yang sama dengan contoh sebelumnya adalah sebagai berikut.

text Hasil perintah queue:failed
  2026-11-03 14:50:00 7fc2f34c-f960-4e90-bfbe-8163b1a44e2a  database@default App\Jobs\EksporCatatan  

Job yang gagal dikembalikan ke antrian dengan queue:retry, baik berdasarkan identifier maupun dengan kata kunci all untuk semuanya. Perintah berikut mengulang seluruh job gagal lalu memprosesnya kembali dengan worker.

bash
# mengembalikan seluruh job gagal ke antrian lalu memprosesnya ulang
cd ~/aplikasi-catatan
php artisan queue:retry all | grep INFO
php artisan queue:work --stop-when-empty --tries=1 --quiet
sqlite3 database/database.sqlite "select count(*) from failed_jobs;"
text Keluaran
   INFO  Pushing failed queue jobs back onto the queue.  
1

Tabel failed_jobs tetap berisi satu baris setelah percobaan ulang karena job yang diulang tetap gagal dengan penyebab yang sama, yaitu format xml yang memang belum didukung. Perintah queue:retry hanya memindahkan job dari tabel failed_jobs kembali ke tabel jobs, sedangkan perbaikan penyebabnya tetap merupakan pekerjaan pada kode job itu sendiri.

Memperbaiki job yang gagal#

Perbaikan pada contoh ini berupa argumen format yang benar. Job EksporCatatan berhasil ketika formatnya csv, sehingga mengirim job baru dengan format tersebut menghasilkan eksekusi yang berhasil tanpa menambah baris gagal.

bash
# mengirim job dengan format yang didukung lalu memprosesnya
cd ~/aplikasi-catatan
php artisan queue:clear --force > /dev/null
php artisan queue:flush > /dev/null
php artisan tinker --execute='
use App\Jobs\EksporCatatan;
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\Queue;
$kerja = Category::firstOrCreate(["nama" => "Kerja"]);
$catatan = Note::firstOrCreate(
    ["judul" => "Pengingat arsip bulanan"],
    ["isi" => "Menandai catatan yang perlu diarsipkan pada akhir bulan.", "category_id" => $kerja->id]
);
Queue::push(new EksporCatatan($catatan, "csv"));
'
php artisan queue:work --stop-when-empty --quiet
text Keluaran
Catatan Pengingat arsip bulanan diekspor ke berkas CSV

Kelas job juga dapat menyiapkan langkah pembersihan untuk keadaan gagal melalui metode failed(), yaitu metode yang dipanggil Laravel ketika job dinyatakan gagal, misalnya untuk memberitahu tim atau menghapus berkas sementara yang belum selesai.

php Contoh: metode failed() pada kelas job
// tidak-dijalankan
public function failed(?Throwable $exception): void
{
    // dikirim ketika job dinyatakan gagal
    Log::error("Ekspor catatan gagal: {$exception->getMessage()}");
}

Seluruh baris pada tabel failed_jobs dihapus dengan queue:flush, sedangkan satu baris tertentu dihapus dengan queue:forget yang menerima identifier-nya. Keduanya berguna untuk membersihkan riwayat kegagalan setelah penyebabnya sudah ditangani.

Kesalahan umum#

Kesalahan pertama adalah tetap menjalankan worker lama setelah kode job diperbaiki. Worker sebagai daemon memuat kode aplikasi sekali di awal, sehingga perbaikan pada kelas job tidak terbaca sampai prosesnya dihidupkan ulang. Perintah php artisan queue:restart mengirim sinyal agar seluruh worker menghentikan diri setelah job saat ini selesai, dan supervisor menghidupkannya kembali dengan kode terbaru.

Kesalahan kedua adalah menyetel retry_after lebih kecil daripada durasi job terlama. Worker lain, atau worker yang sama, menganggap job tersebut hilang setelah retry_after berlalu dan mengeksekusi salinan barunya, padahal eksekusi yang lama masih berjalan. Akibatnya, pekerjaan yang sama dijalankan ganda. Nilai retry_after harus lebih besar daripada durasi job terpanjang yang wajar pada aplikasi.

Kesalahan ketiga adalah mengulang job gagal berulang kali tanpa memperbaiki penyebabnya. Setiap queue:retry hanya mengembalikan job ke antrian, dan job yang sama akan berpindah lagi ke failed_jobs setiap kali melempar error yang sama. Periksa pesan errornya pada tabel failed_jobs terlebih dahulu, perbaiki kode atau datanya, lalu jalankan ulang jobnya.

Ringkasan#

  • Worker dijalankan dengan queue:work sebagai daemon, dengan --stop-when-empty untuk berhenti setelah antrian kosong, atau dengan --once untuk satu job saja.
  • Job yang melempar error diulang sampai batas $tries atau --tries, dengan jeda dari $backoff atau --backoff, lalu dipindahkan ke tabel failed_jobs.
  • queue:failed menampilkan daftar job gagal, queue:retry mengembalikannya ke antrian, sedangkan queue:flush dan queue:forget menghapusnya dari daftar gagal.
  • Metode failed() dipanggil ketika job dinyatakan gagal, dan queue:restart mengirim sinyal agar daemon memuat ulang kode terbaru.

Referensi#

  • Queues — Laravel — Dokumentasi resmi worker antrian, percobaan ulang, backoff, dan penanganan job gagal.
  • Deployment — Laravel — Dokumentasi konfigurasi supervisor untuk menjaga worker tetap berjalan di server produksi.