Table of Contents
▼- Kapan Kamu Butuh Queue: Kasus Nyata Aplikasi Web Indonesia
- Konfigurasi Laravel Queue dengan Redis di VPS Ubuntu
- Membuat Job Class yang Idempoten dan Aman untuk Diretry
- Menjalankan Queue Worker dengan Supervisor agar Tidak Mati Sendiri
- Monitoring Queue Gagal dengan Laravel Horizon dan Alert Telegram
- Tips Production Tambahan
Setiap aplikasi web yang serius pasti punya satu masalah yang sama: ada proses yang terlalu berat untuk dijalankan saat user menunggu respons.
Kirim email massal, generate laporan PDF, resize foto, panggil API pihak ketiga — semua ini bisa memakan waktu 2 sampai 10 detik. Kalau diproses secara sinkron, user hanya akan melihat loading spinner dan akhirnya abandon.
Laravel Queue hadir untuk memecahkan masalah ini. Kamu bisa "melempar" pekerjaan berat ke background, lalu server memprosesnya secara asinkron tanpa membuat user menunggu.
Tutorial ini akan membawa kamu dari nol sampai sistem queue production yang solid — lengkap dengan Redis, Supervisor, Laravel Horizon, dan alert Telegram kalau ada job yang gagal.
Kapan Kamu Butuh Queue: Kasus Nyata Aplikasi Web Indonesia
Banyak developer Indonesia baru sadar butuh queue setelah aplikasinya sudah di production dan mulai menerima komplain "loading lama".
Berikut skenario paling umum yang terjadi:
Aplikasi e-commerce UMKM — Setiap transaksi sukses harus kirim email konfirmasi, update stok di gudang, dan notifikasi ke WhatsApp seller. Kalau tiga proses ini dijalankan bersamaan saat checkout, response time bisa 5–8 detik.
Sistem klinik atau booking — Setelah pasien daftar, sistem harus kirim SMS reminder, simpan ke Google Calendar, dan sync ke sistem billing. Semua ini tidak perlu selesai sebelum user melihat halaman konfirmasi.
Platform e-learning — Upload video membutuhkan transcoding. Proses ini bisa 10 menit. Tidak masuk akal meminta user menunggu.
Laporan dan export data — Generate file Excel dari 10 ribu baris data tidak cocok diproses real-time.
Intinya, kalau ada proses yang butuh lebih dari 1 detik dan tidak perlu hasilnya langsung ditampilkan ke user, itu kandidat kuat untuk dijadikan background job.
Konfigurasi Laravel Queue dengan Redis di VPS Ubuntu
Prasyarat
Sebelum mulai, pastikan VPS kamu sudah memiliki:
- Ubuntu 22.04 atau 24.04
- PHP 8.2+ dengan ekstensi
redis - Laravel 10 atau 11
- Redis terinstall dan berjalan
Install Redis di VPS
sudo apt update
sudo apt install redis-server -y
sudo systemctl enable redis-server
sudo systemctl start redis-server
Verifikasi Redis berjalan:
redis-cli ping
# Output: PONG
Install Predis atau phpredis
Laravel mendukung dua driver Redis. Rekomendasi untuk production adalah ekstensi phpredis (native, lebih cepat).
sudo apt install php8.2-redis -y
sudo systemctl restart php8.2-fpm
Atau pakai Predis via Composer kalau tidak mau install ekstensi:
composer require predis/predis
Konfigurasi .env
Ubah konfigurasi queue di file .env aplikasi Laravel kamu:
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Kalau Redis kamu menggunakan password (sangat disarankan di production):
# Set password di /etc/redis/redis.conf
requirepass password_kamu_yang_kuat
Lalu update .env:
REDIS_PASSWORD=password_kamu_yang_kuat
Verifikasi Konfigurasi Queue
Cek file config/queue.php dan pastikan bagian redis sudah benar:
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
Membuat Job Class yang Idempoten dan Aman untuk Diretry
Ini bagian yang sering dilewati developer pemula, padahal ini krusial di production.
Idempoten artinya: kalau job yang sama dijalankan dua kali, hasilnya tetap sama dan tidak menyebabkan data duplikat atau efek samping yang tidak diinginkan.
Job bisa dieksekusi lebih dari sekali karena timeout, worker restart, atau kegagalan jaringan. Kalau job kamu tidak idempoten, kamu bisa kirim email dua kali ke customer, atau memotong stok dua kali.
Buat Job Class
php artisan make:job SendOrderConfirmationEmail
Ini akan membuat file di app/Jobs/SendOrderConfirmationEmail.php.
Contoh Job yang Idempoten
<?php
namespace App\Jobs;
use App\Models\Order;
use App\Models\EmailLog;
use App\Mail\OrderConfirmation;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
class SendOrderConfirmationEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Jumlah maksimal percobaan ulang
public int $tries = 3;
// Timeout per eksekusi (detik)
public int $timeout = 60;
// Backoff antar retry (detik)
public array $backoff = [30, 60, 120];
public function __construct(
public readonly int $orderId
) {}
public function handle(): void
{
$order = Order::findOrFail($this->orderId);
// Cek apakah email sudah pernah dikirim (idempotency check)
$alreadySent = EmailLog::where('order_id', $this->orderId)
->where('type', 'order_confirmation')
->exists();
if ($alreadySent) {
Log::info("Email konfirmasi order #{$this->orderId} sudah dikirim sebelumnya. Skip.");
return;
}
// Kirim email
Mail::to($order->customer_email)
->send(new OrderConfirmation($order));
// Catat log pengiriman
EmailLog::create([
'order_id' => $this->orderId,
'type' => 'order_confirmation',
'sent_at' => now(),
]);
Log::info("Email konfirmasi order #{$this->orderId} berhasil dikirim.");
}
public function failed(\Throwable $exception): void
{
Log::error("Gagal kirim email untuk order #{$this->orderId}: " . $exception->getMessage());
// Di sini kamu bisa trigger notifikasi ke Telegram atau Slack
}
}
Dispatch Job dari Controller
// Di OrderController.php setelah transaksi sukses
use App\Jobs\SendOrderConfirmationEmail;
SendOrderConfirmationEmail::dispatch($order->id);
// Atau dengan delay 5 menit
SendOrderConfirmationEmail::dispatch($order->id)->delay(now()->addMinutes(5));
// Atau ke queue khusus
SendOrderConfirmationEmail::dispatch($order->id)->onQueue('emails');
Menjalankan Queue Worker dengan Supervisor agar Tidak Mati Sendiri
Queue worker di Laravel dijalankan via command php artisan queue:work. Masalahnya, kalau terminal ditutup atau server restart, worker mati.
Supervisor adalah process control system di Linux yang memastikan worker selalu berjalan dan otomatis restart kalau mati.
Install Supervisor
sudo apt install supervisor -y
sudo systemctl enable supervisor
sudo systemctl start supervisor
Buat Konfigurasi Supervisor untuk Laravel Queue
sudo nano /etc/supervisor/conf.d/laravel-queue.conf
Isi dengan konfigurasi berikut:
[program:laravel-queue-default]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/aplikasi-kamu/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --queue=default
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/aplikasi-kamu/storage/logs/queue-worker.log
stopwaitsecs=3600
[program:laravel-queue-emails]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/aplikasi-kamu/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --queue=emails
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/aplikasi-kamu/storage/logs/queue-emails.log
stopwaitsecs=3600
Konfigurasi di atas menjalankan 2 worker untuk queue default dan 1 worker khusus untuk queue emails.
Aktifkan dan Reload Supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start all
sudo supervisorctl status
Output yang diharapkan:
laravel-queue-default:laravel-queue-default_00 RUNNING pid 12345, uptime 0:02:15
laravel-queue-default:laravel-queue-default_01 RUNNING pid 12346, uptime 0:02:15
laravel-queue-emails:laravel-queue-emails_00 RUNNING pid 12347, uptime 0:02:15
Restart Worker Setelah Deploy
Setiap kali deploy kode baru, kamu wajib restart worker agar perubahan kode diambil:
sudo supervisorctl restart laravel-queue-default:*
sudo supervisorctl restart laravel-queue-emails:*
Atau tambahkan perintah ini di script deploy otomatis kamu:
php artisan queue:restart
Perintah queue:restart akan memberi sinyal ke semua worker untuk selesaikan job yang sedang berjalan, lalu restart dengan graceful.
Monitoring Queue Gagal dengan Laravel Horizon dan Alert Telegram
Install Laravel Horizon
Horizon adalah dashboard resmi dari Laravel untuk monitoring queue berbasis Redis.
composer require laravel/horizon
php artisan horizon:install
php artisan migrate
Konfigurasi Horizon
Edit file config/horizon.php untuk mengatur environment production:
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
Jalankan Horizon via Supervisor
Ganti konfigurasi Supervisor sebelumnya dengan Horizon (Horizon sudah include queue worker di dalamnya):
[program:laravel-horizon]
process_name=%(program_name)s
command=php /var/www/aplikasi-kamu/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/aplikasi-kamu/storage/logs/horizon.log
stopwaitsecs=3600
Reload Supervisor:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-horizon
Proteksi Dashboard Horizon
Secara default, Horizon hanya bisa diakses di environment local. Di production, tambahkan authorization di app/Providers/HorizonServiceProvider.php:
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return in_array($user->email, [
'[email protected]',
]);
});
}
Akses dashboard di: https://aplikasikamu.com/horizon
Alert Telegram untuk Job Gagal
Mengetahui job gagal secara real-time sangat penting di production. Berikut cara setup alert via Telegram.
Pertama, buat bot Telegram dan dapatkan BOT_TOKEN serta CHAT_ID. Lalu tambahkan ke .env:
TELEGRAM_BOT_TOKEN=123456:ABCdef...
TELEGRAM_CHAT_ID=-100123456789
Buat helper service untuk kirim notifikasi:
// app/Services/TelegramNotifier.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class TelegramNotifier
{
public static function send(string $message): void
{
$token = config('services.telegram.bot_token');
$chatId = config('services.telegram.chat_id');
if (!$token || !$chatId) {
return;
}
Http::post("https://api.telegram.org/bot{$token}/sendMessage", [
'chat_id' => $chatId,
'text' => $message,
'parse_mode' => 'HTML',
]);
}
}
Tambahkan ke config/services.php:
'telegram' => [
'bot_token' => env('TELEGRAM_BOT_TOKEN'),
'chat_id' => env('TELEGRAM_CHAT_ID'),
],
Panggil di method failed() di setiap Job class:
public function failed(\Throwable $exception): void
{
$message = "🚨 <b>Job Gagal!</b>\n"
. "Job: <code>" . class_basename($this) . "</code>\n"
. "Order ID: <code>{$this->orderId}</code>\n"
. "Error: <code>" . $exception->getMessage() . "</code>\n"
. "Waktu: " . now()->format('d/m/Y H:i:s');
\App\Services\TelegramNotifier::send($message);
}
Sekarang setiap kali job gagal setelah semua retry habis, kamu akan langsung dapat notifikasi di Telegram.
Tips Production Tambahan
Beberapa hal yang sering dilupakan saat deploy queue ke production:
Pastikan failed_jobs table ada.
php artisan queue:failed-table
php artisan migrate
Cek failed jobs secara berkala.
php artisan queue:failed
php artisan queue:retry all
php artisan queue:flush # Hapus semua failed jobs
Gunakan --max-time bukan hanya --tries untuk mencegah worker zombie yang stuck terlalu lama.
Jangan simpan data besar di job payload. Kirim ID, bukan seluruh objek Eloquent. Laravel memang punya SerializesModels, tapi kalau model punya relasi besar, payload bisa membengkak.
Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
Laravel Queue bukan fitur "nice to have" — ini kebutuhan dasar aplikasi bisnis yang serius. Dengan setup Redis, Supervisor, dan Horizon yang benar, kamu punya sistem background job yang reliable, mudah dimonitor, dan langsung alert kalau ada yang bermasalah.
Mulai dari satu use case sederhana, misalnya memindahkan pengiriman email ke background. Setelah itu, kamu akan mulai melihat banyak tempat lain di aplikasi yang bisa dioptimalkan dengan pola yang sama.