Cara Optimasi Laravel Queue Worker untuk High Concurrency

Cara Optimasi Laravel Queue Worker untuk High Concurrency


NEW
laravel redis devops backend

Cara optimasi Laravel queue worker untuk high concurrency adalah solusi krusial saat aplikasi Laravel mengalami lonjakan job seperti pengiriman email massal, pemrosesan transaksi, atau kalkulasi Laporan bulanan. Ketika ribuan job masuk secara bersamaan, queue worker yang tidak dikonfigurasi dengan tepat bisa mengalami memory leak, race condition, hingga kehabisan koneksi database.

Dalam panduan ini, kita akan membahas strategi praktis untuk mengoptimalkan Laravel queue worker agar sanggup menangani skalabilitas tinggi secara stabil dan efisien.

Kenapa Queue Worker Sering Menjadi Bottleneck?

Secara default, perintah php artisan queue:work menjalankan proses PHP persisten yang terus aktif di background. Berbeda dengan HTTP request biasa yang memuat ulang seluruh framework pada setiap hit, queue worker memuat aplikasi ke memori hanya sekali saat pertama kali dihidupkan.

Karakteristik persisten ini memberikan performa eksekusi yang sangat cepat. Namun, ada beberapa kelemahan utama jika tidak dikonfigurasi dengan benar:

  • Memory Leak: Objek yang tersimpan di variabel statis atau Event Listener tidak dibersihkan secara otomatis antar eksekusi job.
  • Koneksi Database Putus: Koneksi PostgreSQL atau MySQL yang idle dapat terputus (MySQL server has gone away).
  • Single Process Bottleneck: Satu worker hanya bisa memproses satu job dalam satu waktu (single-threaded execution).
  • Resource Contention: Terlalu banyak worker paralel tanpa batasan CPU/RAM akan membebani server hosting atau VPS.

1. Gunakan Redis sebagai Queue Driver Utama

Hindari penggunaan driver database untuk sistem dengan high concurrency. Driver database melakukan polling tabel jobs menggunakan query SELECT dan DELETE berulang kali, yang menciptakan lock contention pada tabel database.

Ubah konfigurasi queue driver di file .env ke Redis:

QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis

Gunakan extension phpredis (C extension) daripada predis karena memiliki throughput throughput query yang jauh lebih tinggi dengan konsumsi CPU lebih rendah.

2. Manajemen Supervisor untuk Multi-Process Worker

Untuk menangani ribuan job secara sejajar, kita perlu menjalankan beberapa instance worker sekaligus menggunakan Supervisor.

Berikut adalah contoh konfigurasi Supervisor di /etc/supervisor/conf.d/laravel-worker.conf:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/my-app/artisan queue:work redis --sleep=3 --tries=3 --max-jobs=1000 --max-time=3600 --timeout=90
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/my-app/storage/logs/worker.log
stopwaitsecs=3600

Penjelasan Parameter Penting:

  1. --max-jobs=1000: Worker otomatis restart setelah memproses 1000 job untuk mencegah akumulasi memory leak.
  2. --max-time=3600: Worker restart setiap 1 jam sekali secara aman.
  3. numprocs=8: Menjalankan 8 proses worker independen yang bekerja secara seimbang.
  4. stopwaitsecs=3600: Memberi waktu worker menyelesaikan job yang sedang berjalan sebelum dimatikan saat deployment (queue:restart).

3. Pisahkan Queue Berdasarkan Prioritas

Jangan mencampur seluruh jenis job dalam satu antrean default. Pisahkan job berdasarkan prioritas eksekusi: high, default, dan low.

Contoh dispatch job dengan prioritas:

// Job pembayaran harus cepat diproses
ProcessPayment::dispatch($order)->onQueue('high');

// Job kirim notifikasi email biasa
SendNotificationEmail::dispatch($user)->onQueue('default');

// Job generate laporan bulanan yang berat
GenerateMonthlyReport::dispatch($period)->onQueue('low');

Jalankan worker dengan urutan prioritas di Supervisor:

php artisan queue:work redis --queue=high,default,low

Worker akan selalu memprioritaskan antrean high. Antrean default dan low hanya diproses ketika antrean high dalam kondisi kosong.

4. Gunakan Laravel Horizon untuk Dynamic Scaling

Jika menggunakan Redis, Laravel Horizon menyediakan dashboard monitoring real-time dan fitur auto-scaling worker berbasis beban antrean.

Install Laravel Horizon:

composer require laravel/horizon
php artisan horizon:install

Konfigurasi config/horizon.php untuk auto-scaling:

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['high', 'default', 'low'],
            'balance' => 'auto',
            'autoScalingStrategy' => 'time',
            'minProcesses' => 3,
            'maxProcesses' => 20,
            'balanceMaxShift' => 2,
            'balanceCooldown' => 3,
        ],
    ],
],

Dengan konfigurasi balance => 'auto', Horizon akan menambah jumlah worker otomatis hingga 20 proses jika antrean menumpuk, dan menguranginya hingga 3 proses ketika antrean kembali sepi.

5. Cegah Memory Leak dan Database Disconnection

Untuk memastikan kestabilan long-running process pada Laravel worker, terapkan praktek berikut pada kode job:

Unset Variabel Besar dan Clear Memory

Setelah memproses data dalam jumlah besar (misalnya ekspor jutaan baris CSV), bersihkan memori secara manual:

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;

class ExportBigDataJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public function handle(): void
    {
        $data = HeavyModel::lazy();

        foreach ($data as $row) {
            // Process row...
        }

        unset($data);
        gc_collect_cycles(); // Trigger garbage collector
    }
}

Gunakan Chunking / Lazy Loading

Hindari penggunaan Model::all() atau get() untuk dataset ribuan record. Gunakan chunk() atau lazy() untuk menghemat penggunaan RAM.

User::where('active', true)->chunk(500, function ($users) {
    foreach ($users as $user) {
        // Process user
    }
});

Takeaways Ringkas

  • Prioritaskan driver Redis dibanding database untuk performa I/O antrean terbaik.
  • Manfaatkan Supervisor atau Laravel Horizon untuk menjalankan multi-process worker.
  • Atur parameter --max-jobs dan --max-time untuk mencegah masalah memory leak.
  • Pisahkan antrean berdasarkan prioritas (high, default, low) agar job kritis tidak tertahan.
  • Gunakan lazy() atau chunk() pada database query di dalam job handler.

Dengan menerapkan strategi di atas, infrastruktur antrean Laravel kamu akan siap menangani jutaan transaksi harian tanpa khawatir server runtuh akibat kehabisan memori atau koneksi database.

© 2026 D.N. Zaki Zamani