“Bu queue yerelde çalışıyordu” cümlesi yedi senedir aynı tonla söyleniyor. Production’da çalışıyor — sadece beklediğiniz hızda değil. Gerçek nedenler genelde Laravel’in dışında.
1. Redis için yeterli connection pool yok
Default phpredis ya da predis her job için yeni bağlantı kuruyor. Worker
başına ortalama bir TCP handshake’lik gecikme + occasional ECONNRESET.
Çözüm: config/database.php içinde persistent => true (phpredis için) veya
pgBouncer benzeri bir Redis proxy katmanı (RedisCluster için doğal). Worker
başlattığınızda bağlantı havuzunu open bırakın:
'redis' => [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', ''),
'persistent' => true,
],
],
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.25’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’i yeniden başlatmıyorsunuz
Long-running PHP processler memory’i biriktirir. --max-time=3600 veya
--max-jobs=1000 ile worker’lar periyodik olarak kendilerini sonlandırıp
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 koy ve worker --queue=payments,default,low
şeklinde sıralı dinlesin.
Görmesi gereken metrikler
Üç şey yeterli:
- Kuyruk uzunluğu (Redis:
LLEN). - Job süresi p95 (Horizon veya kendi instrumentation’ınız).
- 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ı.