Antrian menunda eksekusi pekerjaan berat agar permintaan HTTP segera memperoleh respons tanpa menunggu pekerjaan itu selesai. Topik ini membahas konsep antrian (queue) pada Laravel beserta driver yang menyimpan job-nya, cara mendefinisikan kelas job, dan cara mengirim job ke antrian. Setelah membacanya, tugas latar belakang seperti pengiriman pengingat dapat dipindahkan keluar dari siklus permintaan pada aplikasi catatan menggunakan driver database.
Antrian dan job dalam siklus permintaan#
Tanpa antrian, tugas seperti mengirim surel atau menghasilkan laporan berjalan di dalam permintaan yang sama dengan aksi pengguna, sehingga waktu respons ikut memuat lamanya tugas tersebut. Laravel memisahkan keduanya menjadi dua proses yang berbeda. Aplikasi hanya menyimpan deskripsi pekerjaan ke dalam media penyimpanan antrian. Deskripsi tersebut berupa job, yakni kelas PHP yang membungkus satu pekerjaan beserta data yang dibutuhkannya, sedangkan eksekusinya dikerjakan oleh worker, yakni proses terpisah yang mengambil job dari antrian dan menjalankannya, sehingga pekerjaan itu tidak lagi membebani permintaan web.
Alur kerjanya terdiri atas tiga tahap yang berurutan. Aplikasi mengirim job dengan dispatch, yaitu menyimpan salinan job beserta datanya ke media antrian, kemudian segera melanjutkan alur permintaan. Media antrian pada driver database berupa tabel jobs yang barisnya menunggu untuk diambil. Worker mengambil baris tersebut, memanggil metode handle() milik job, lalu menghapus barisnya setelah selesai.
Driver antrian yang tersedia#
Driver menentukan di mana job disimpan sebelum diproses. Daftar lengkapnya dikonfigurasikan pada berkas config/queue.php, sedangkan driver yang aktif dipilih melalui variabel QUEUE_CONNECTION pada berkas .env. Proyek aplikasi catatan menggunakan nilai database, sehingga job disimpan ke tabel jobs pada SQLite.
| Driver | Media penyimpanan | Kebutuhan |
|---|---|---|
sync | Tanpa media, eksekusi segera | Tidak ada |
database | Tabel jobs pada database aplikasi | Migration tabel antrian |
redis | List pada server Redis | Server Redis dan ekstensi phpredis atau paket predis |
beanstalkd | Tube pada server Beanstalkd | Server Beanstalkd |
sqs | Layanan AWS SQS | Kredensial AWS |
Driver sync tidak menyimpan job ke mana pun karena metode handle() dieksekusi segera pada saat dispatch di dalam proses yang sama. Driver ini praktis untuk pengembangan karena perilakunya persis seperti pemanggilan metode biasa, tetapi seluruh keuntungan antrian hilang bersama driver ini. Driver database menuntut keberadaan tabel jobs, job_batches, dan failed_jobs yang sudah disediakan oleh migration bawaan Laravel, sehingga proyek aplikasi catatan dapat langsung menggunakannya tanpa persiapan tambahan. Driver redis menjadi pilihan umum pada aplikasi dengan trafik tinggi karena pembacaan dan penulisannya berlangsung di memori, namun kebutuhan server tambahannya membuat pembahasan pada topik ini tetap berfokus pada driver database.
Membuat kelas job#
Kelas job dibuat dengan perintah make:job, dan Laravel menempatkannya di direktori app/Jobs. Perintah berikut membuat job bernama KirimPengingat yang akan memproses pengingat untuk satu catatan.
# tidak-dijalankan
# membuat kelas job baru bernama KirimPengingat
cd ~/aplikasi-catatan
php artisan make:job KirimPengingatPerintah tersebut tidak dieksekusi ulang pada contoh ini karena berkasnya sudah dibuat. Keluaran asli dari perintah itu adalah sebagai berikut.
INFO Job [app/Jobs/KirimPengingat.php] created successfully. Berkas hasil perintah tersebut disunting agar job menerima satu model Note dan menjalankan tugasnya pada handle(). Teks yang ditampilkan dengan echo digunakan di sini sebagai penanda bahwa job benar-benar dieksekusi, sedangkan pada aplikasi nyata isi handle() berupa pekerjaan sesungguhnya seperti mengirim surel atau menulis arsip.
// tidak-dijalankan
<?php
namespace App\Jobs;
use App\Models\Note;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class KirimPengingat implements ShouldQueue
{
use Queueable;
/**
* Create a new job instance.
*/
public function __construct(public Note $note)
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
echo "Pengingat untuk catatan: {$this->note->judul}" . PHP_EOL;
}
}Dua bagian pada kelas job menentukan perilaku antriannya. Implementasi antarmuka ShouldQueue membuat Laravel menempatkan job ke antrian alih-alih menjalankannya segera, sedangkan trait Queueable yang dihasilkan make:job turut membawa trait SerializesModels. Trait SerializesModels membuat model yang diteruskan ke konstruktor hanya diserialisasi menjadi nilai kunci utamanya, lalu model utuh diambil ulang dari database ketika worker memproses job, sehingga payload yang tersimpan tetap ringan dan data yang dibaca selalu terkini.
Mengirim job ke antrian#
Job yang sudah didefinisikan dikirim ke antrian melalui dispatch, yaitu pemanggilan Queue::push() atau pemanggilan statis dispatch() yang disediakan trait Dispatchable. Keduanya menghasilkan payload yang sama, sehingga pilihan dipengaruhi gaya penulisan semata.
// tidak-dijalankan
use App\Jobs\KirimPengingat;
use Illuminate\Support\Facades\Queue;
Queue::push(new KirimPengingat($catatan));
// bentuk lain yang setara
KirimPengingat::dispatch($catatan);Contoh berikut mengirim satu job KirimPengingat dari Tinker setelah antrian dikosongkan dan data catatannya disiapkan secara deterministik.
# menyiapkan antrian kosong lalu mengirim job pengingat
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"]);
$catatan = Note::firstOrCreate(
["judul" => "Pengingat arsip bulanan"],
["isi" => "Menandai catatan yang perlu diarsipkan pada akhir bulan.", "category_id" => $kerja->id]
);
Queue::push(new KirimPengingat($catatan));
echo "Ukuran antrian: " . Queue::size() . PHP_EOL;
'Ukuran antrian: 1Baris yang baru saja dibuat dapat diperiksa langsung pada tabel jobs untuk melihat apa yang sebenarnya disimpan oleh dispatch. Kolom queue memuat nama antrian, kolom attempts mencatat berapa kali job telah dicoba, dan kolom reserved_at bernilai NULL karena belum ada worker yang memegang job ini.
# mengamati baris baru pada tabel jobs
cd ~/aplikasi-catatan
sqlite3 database/database.sqlite "select queue, attempts, reserved_at from jobs;"
sqlite3 database/database.sqlite "select json_extract(payload, '$.displayName') from jobs;"default|0|
App\Jobs\KirimPengingatKolom payload menyimpan seluruh job dalam bentuk JSON, dan displayName pada payload itulah nama kelas yang dikenali worker. Nama antrian default berasal dari konfigurasi koneksi database pada config/queue.php yang menetapkan DB_QUEUE bernilai default.
Memproses job dengan worker sekali jalan#
Job yang menunggu di tabel jobs dieksekusi oleh worker yang dijalankan dengan perintah queue:work. Opsi --stop-when-empty membuat worker memproses seluruh job yang tersedia lalu berhenti ketika antrian kosong, sedangkan opsi --quiet meredam pesan status worker sehingga hanya keluaran dari job yang tampak.
# memproses seluruh job yang menunggu lalu berhenti
cd ~/aplikasi-catatan
php artisan queue:work --stop-when-empty --quiet
php artisan tinker --execute='
use Illuminate\Support\Facades\Queue;
echo "Sisa job: " . Queue::size() . PHP_EOL;
'Pengingat untuk catatan: Pengingat arsip bulanan
Sisa job: 0Keluaran tersebut memperlihatkan dua bukti sekaligus. Baris pertama berasal dari echo pada metode handle(), yang berarti job benar-benar dieksekusi oleh worker, sedangkan baris kedua menunjukkan tabel jobs sudah kosong karena baris job dihapus setelah selesai diproses. Tanpa opsi --quiet, worker menampilkan baris status RUNNING dan DONE untuk setiap job beserta waktu pemrosesannya.
Menunda eksekusi job#
Dispatch tidak selalu berarti eksekusi segera. Metode Queue::later() menerima penundaan berupa jumlah detik atau instance DateTimeInterface, sehingga job hanya akan tersedia untuk worker setelah waktunya tiba. Pada driver database, penundaan itu diwujudkan sebagai kolom available_at berisi nilai timestamp unix.
# mengosongkan antrian lalu mengirim job dengan penundaan 300 detik
cd ~/aplikasi-catatan
php artisan queue:clear --force > /dev/null
php artisan tinker --execute='
use App\Jobs\KirimPengingat;
use App\Models\Note;
use Illuminate\Support\Facades\Queue;
Queue::later(300, new KirimPengingat(Note::first()));
echo "Ukuran antrian: " . Queue::size() . PHP_EOL;
'
sqlite3 database/database.sqlite "select available_at - created_at from jobs;"Ukuran antrian: 1
300Job tetap terhitung sebagai satu job yang menunggu, tetapi selisih available_at dengan created_at senilai 300 detik memperlihatkan bahwa job baru tersedia lima menit kemudian. Worker tidak akan mengambil job ini sebelum waktunya tiba, meskipun antrian tidak berisi job lain. Bentuk dengan objek tanggal tertentu, misalnya Queue::later(now()->addMinutes(5), new KirimPengingat($catatan)), menghasilkan perilaku yang sama karena Laravel menghitung selisihnya menjadi detik.
Kesalahan umum#
Kesalahan pertama adalah mengirim job dari kelas yang tidak mengimplementasikan ShouldQueue. Pemanggilan new KirimPengingat($catatan) tanpa dispatch memang masih mengeksekusi handle(), tetapi semuanya terjadi sinkron di dalam permintaan, sehingga pengguna tetap menunggu pekerjaan beratnya selesai. Pastikan kelasnya berasal dari make:job atau paling tidak mengimplementasikan Illuminate\Contracts\Queue\ShouldQueue.
Kesalahan kedua adalah mengamati tabel jobs pada driver sync dan menganggap antrian tidak berjalan. Driver sync tidak pernah menulis baris ke tabel karena eksekusinya segera, sehingga tabel kosong adalah perilaku yang benar. Ganti QUEUE_CONNECTION menjadi database terlebih dahulu apabila proses antrian perlu diamati langsung pada tabelnya.
Kesalahan ketiga adalah lupa menjalankan worker sama sekali. Job terkirim dan barisnya menumpuk di tabel jobs, tetapi tidak ada proses yang mengambil dan mengeksekusinya. Dispatch hanya menyimpan pekerjaan, sedangkan eksekusinya selalu merupakan tugas worker.
Ringkasan#
- Job adalah kelas PHP yang mengimplementasikan
ShouldQueue, memuat data pada konstruktor, dan menjalankan pekerjaannya pada metodehandle(). - Driver menentukan media penyimpanan antrian, yaitu tabel
jobsuntuk driver database dan list pada server Redis untuk driver redis, sedangkan driver sync mengeksekusi job segera tanpa media. - Dispatch mengirim job ke antrian melalui
Queue::push()atauKirimPengingat::dispatch(), danQueue::size()menghitung jumlah job yang menunggu. Queue::later()menunda eksekusi dengan mengatur kolomavailable_at, dan job hanya diambil worker setelah waktu tersebut tercapai.
Referensi#
- Queues — Laravel — Dokumentasi resmi antrian Laravel, mencakup driver, kelas job, dispatch, dan penundaan eksekusi.
- Redis — Laravel — Dokumentasi koneksi Redis yang menjadi prasyarat driver antrian redis.