“Bu queue yerelde çalışıyordu” cümlesi senelerdir aynı tonla söyleniyor. Production’da çalışıyor — sadece beklediğiniz hızda değil. Gerçek nedenler genelde Laravel’in dışında.
1. Queue Storage için yeterli connection pool yok
Queue Storage olarak Redis kullandığınızı varsayalım.
Default phpredis ya da predis bağlantıyı job başına değil worker süreci başına kuruyor — queue:work uzun ömürlü tek bir süreçtir ve Laravel’in RedisManager’ı çözümlediği bağlantıyı önbelleğe alır. Her job için yeni süreç doğan kurulumlarda (queue:listen, PHP-FPM istekleri) bağlantı gerçekten job başına açılır: her birinde bir TCP handshake’lik gecikme + arada bir ECONNRESET.
Çözüm: config/database.php içinde persistent => true (phpredis için) — Laravel aynı seçeneği RedisCluster’a da geçirir, yani cluster kurulumu da kapsanır. Worker başlattığınızda bağlantıyı açık bırakın:
'redis' => [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', ''),
'persistent' => true,
],
],
Not: RabbitMQ tarafında optimizasyonun ana noktası persistent socket flag’i değil, connection lifecycle yönetimidir. Her job’da connection açmak yerine worker process başına uzun ömürlü AMQP connection kullanılmalı, channel reuse yapılmalı ve heartbeat/read-write timeout değerleri job sürelerine göre ayarlanmalıdır.
2. Job içinde sync I/O bekletmesi
Http::get() çağrısı 30 saniye timeout ile bekleyebiliyor. O job o worker’ı tutar. 10 worker, 10 yavaş upstream — kuyruğun gerisi durur.
İki kural:
- Her HTTP/SQL çağrısının explicit timeout’u olsun. Default 30 saniye değil 5.
- Beklenmesi gereken işler
delay’li ayrı bir kuyrukta tutulsun (örn. webhook retry’leri içinslowkuyruğu, hızlı işlerdefaultkuyruğunda).
3. Supervisor numprocs yanlış
Tek bir queue worker tek bir PHP process’tir. Tek bir PHP process tek bir CPU core’unu kullanır. 4 core’lu sunucuda 1 worker çalıştırmak nproc * 0.75’i atıl bırakıyor demektir.
Tipik kural: numprocs = nproc (CPU-bound) veya numprocs = 2 * nproc (I/O-bound). Her uygulamayı ölçün.
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work redis --queue=default --sleep=1 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=4
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log
stopwaitsecs=3600
stopwaitsecs=3600 önemli — Supervisor restart’ı sırasında uzun bir job’u yarıda kesmesin.
4. Memory leak’e karşı yeniden başlatmıyorsunuz
Long-running PHP process’ler memory’i biriktirir. --max-time=3600 veya --max-jobs=1000 ile worker’lar periyodik olarak kendilerini sonlandırmalı ve Supervisor tarafından yeniden başlatılmalı. Aksi takdirde:
- Worker 8 GB RAM tüketir.
- OOM killer onu öldürür.
- Hangi job’u yarıda kestiği belirsiz.
5. Job batch’leri için tek bir kuyruk kullanmak
Yüksek hacimli “send notification” job’larıyla az sayıda “process payment” job’unu aynı kuyruğa atınca:
- 50.000 notification 30 dakika kuyruğu tıkar.
- Payment job’u bekler.
- Müşteri “ödemem geçmedi” diye yazar.
Prioritize: önemli işleri ayrı kuyruklara koyun ve worker --queue=payments,default,low şeklinde sıralı dinlesin.
Görmeniz gereken metrikler
Üç şey yeterli:
- Kuyruk uzunluğu (Redis: kuyruk listesi için
LLEN— delayed ve reserved job’lar ayrı sorted set’lerde durur, onlarınZCARD’ını da ekleyin). - Job süresi p95 (kendi instrumentation’ınız — Horizon’un metrik panosu throughput, bekleme süresi ve ortalama runtime verir, yüzdelik dilim vermez).
- Failed job sayısı son 5 dakikada.
Bu üçü dashboard’da olsun ve eşik geçince alarm üretsin.
Queue yavaşlığının %90’ı bu listedeki bir şeydir. Geri kalan %10 ise gerçek bottleneck’ler (database, external API) — onları bulmak için doğru ölçümünüz olmalı. Production’da “queue yavaş” hipotez değil, ölçülmüş bir şey olmalı.

Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.