Setiap job yang didispatch harus disimpan di suatu tempat sebelum dikerjakan, dan Laravel menyebut tempat itu queue connection. Kita perlu memilih backend penyimpanannya — tabel database, Redis, SQS milik AWS, atau driver sync yang mengeksekusi langsung — dan pilihan itu ditetapkan lewat satu variabel environment. Pada chapter ini kita bedah file config/queue.php bawaan Laravel 13, memahami tiga tabel pendukung antrean beserta migrasinya, lalu belajar membagi satu connection menjadi beberapa antrean bernama. Di akhir chapter kita juga melihat alasan driver sync menjadi default di lingkungan testing.
QUEUE_CONNECTION di File .env #
Aplikasi Laravel baru membaca pilihan connection dari .env:
QUEUE_CONNECTION=databaseSandbox kita memakai nilai ini, dan Laravel akan menukarnya dengan sync secara otomatis saat testing — mekanisme itu kita buktikan di akhir chapter. Nilai dari variabel tersebut mengalir ke kunci default di config/queue.php:
'default' => env('QUEUE_CONNECTION', 'database'),Artinya: bila QUEUE_CONNECTION tidak diset sama sekali, Laravel tetap memilih connection database. Kita bisa mengganti driver kapan saja tanpa menyentuh kode — cukup ubah .env, karena seluruh kode dispatch tetap memanggil dispatch() tanpa peduli backend di belakangnya.
Membedah config/queue.php #
File konfigurasi ini punya dua bagian penting. Bagian pertama adalah default yang baru kita lihat; bagian kedua adalah array connections yang mendefinisikan setiap backend. Kita ambil tiga yang paling sering dipakai dari file aslinya:
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
// ... beanstalkd, sqs, deferred, background, failover
],Bedah baris pentingnya:
driver— jenis backend yang dipakai; menentukan bagaimana job disimpan dan diambil.table— khusus driverdatabase: nama tabel penampung job, defaultjobs.queue— nama antrean bawaan di connection ini; job tanpa perintah khusus masuk ke antrean bernamadefault.retry_after— setelah 90 detik job dipegang worker tanpa kabar, job dianggap gagal dan boleh diambil worker lain. Nilai ini harus selalu lebih besar daripadatimeoutjob — alasan lengkapnya di chapter Job Retries .after_commit— bilatrue, job yang didispatch di dalam transaksi database menunggu commit dulu sebelum benar-benar masuk antrean.
Menariknya, Laravel 13 juga membawa driver baru bernama deferred dan background di file ini, serta connection failover yang mencoba beberapa connection secara berurutan — database lalu deferred pada instalasi baru.
Tiga Tabel Pendukung Antrean #
Driver database menyimpan segala sesuatunya di tabel, dan Laravel 13 sudah menyertakan migrasinya sejak instalasi lewat satu berkas 0001_01_01_000002_create_jobs_table.php:
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedSmallInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->string('connection');
$table->string('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
$table->index(['connection', 'queue', 'failed_at']);
});Peran ketiganya terpisah tegas:
| Tabel | Peran | Diisi oleh |
|---|---|---|
jobs | Job menunggu giliran dikerjakan | Setiap dispatch() |
job_batches | Metadata grup job untuk batching | Bus::batch() |
failed_jobs | Job yang mengalah setelah percobaan habis | Worker |
Bagi aplikasi lama yang belum memilikinya, perintah php artisan queue:table dan php artisan queue:failed-table menghasilkan migrasi yang sama.
Nama Antrean di dalam Satu Connection #
Satu connection tidak harus berupa satu antrean rata. Method onQueue() mengirim job ke antrean bernama di connection yang sama, dan namanya tercatat di kolom queue:
use App\Models\User;
use App\Jobs\SendWelcomeEmail;
use Illuminate\Support\Facades\DB;
DB::table('jobs')->delete();
$user = User::where('email', '[email protected]')->firstOrFail();
SendWelcomeEmail::dispatch($user);
SendWelcomeEmail::dispatch($user)->onQueue('notifications');
SendWelcomeEmail::dispatch($user)->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:
job_id=1 queue=default
job_id=2 queue=notifications
job_id=3 queue=emailsKetiga baris tinggal di tabel jobs yang sama, hanya dibedakan kolom queue. Manfaat praktisnya dua arah: worker tertentu bisa ditugaskan hanya mengawasi antrean penting (misalnya notifications) agar tidak antre di belakang pekerjaan berat, dan urutan prioritas bisa diatur lewat flag --queue=emails,default — kita praktikkan di chapter Queue Workers
.
Memilih Driver untuk Produksi #
| Driver | Penyimpanan | Cocok untuk |
|---|---|---|
sync | Tidak disimpan — langsung dieksekusi | Testing, mode maintenance sederhana |
database | Tabel jobs di database utama | Aplikasi menengah, tanpa infrastruktur tambahan |
redis | Struktur list di Redis | Throughput tinggi, banyak worker |
sqs | Layanan antrean terkelola AWS | Aplikasi di infrastruktur AWS |
beanstalkd | Server antrean khusus | Sistem yang sudah menjalankan Beanstalk |
Driver database adalah titik awal yang jujur: tanpa server tambahan, transaksional bersama data aplikasi, dan cukup cepat sampai antrean tumbuh sangat besar. Ketika volume naik, Redis menjadi langkah berikutnya yang populer — dan di sanalah package Horizon masuk, memberi dashboard untuk memantau antrean Redis: waktu tunggu, throughput, dan kegagalan job.
Driver sync untuk Testing #
Buka phpunit.xml bawaan Laravel dan kita akan menemukan baris ini:
<env name="QUEUE_CONNECTION" value="sync"/>Dengan driver sync, setiap dispatch() mengeksekusi handle() secara langsung di dalam request yang sama — tanpa menyentuh tabel jobs. Mari kita buktikan dengan mengganti connection saat runtime:
use App\Models\User;
use App\Jobs\SendWelcomeEmail;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
DB::table('jobs')->delete();
Config::set('queue.default', 'sync');
@unlink($dir.'/storage/logs/laravel.log');
$user = User::where('email', '[email protected]')->firstOrFail();
SendWelcomeEmail::dispatch($user);
echo 'jobs_tersisa: ' . DB::table('jobs')->count() . "\n";
echo 'email_masuk_log: ' . var_export(
str_contains(file_get_contents($dir.'/storage/logs/laravel.log'), 'Subject: Selamat Datang!'), true) . "\n";Output yang Dihasilkan:
jobs_tersisa: 0
email_masuk_log: trueTabel jobs tetap kosong namun email sudah terkirim — pekerjaan selesai di tempat, seketika. Perilaku inilah yang membuat test suite lebih cepat dan kegagalan job langsung tampak sebagai kegagalan test, tanpa perlu menjalankan worker. Konsekuensinya, test yang ingin memverifikasi antrean sungguhan perlu mengembalikan queue.default ke database — teknik yang kita pakai di chapter Dispatching Jobs
. Detail perilaku tiap driver bisa dirujuk di dokumentasi Queues
.
Connection telah dipilih dan tabelnya siap. Sekarang saatnya mengisi antrean itu dengan pekerjaan sungguhan: menulis class job pertama kita di chapter Membuat Job .