---
title: "Laravel Queue Production'da Neden Yavaşlar?"
description: "Laravel queue worker'ları yerelde mükemmel, production'da yavaş. Geliştiricilerin gözden kaçırdığı en yaygın beş neden."
url: https://sade.dev/tr/notes/laravel-queue-production-yavaslama/
lang: tr
author: "Muhammet Şafak"
published: 2026-05-03
updated: 2026-09-06
section: Note
tags: ["laravel","queue","production","redis"]
summary: "Queue'nun production'da yavaşlaması genelde Laravel'in içindeki değil, altındaki beş şeyden gelir: job başına açılan bağlantı, job içinde timeout'suz I/O, çekirdek sayısını yok sayan worker sayısı, memory leak'e karşı periyodik restart'ın olmayışı ve tek kuyruğa yığılmış öncelikler. Beşi de ölçülebilir; asıl mesele de bu: queue yavaş, üzerine iş yapacağınız bir hipotez olmamalı."
---

# Laravel Queue Production'da Neden Yavaşlar?

> Queue'nun production'da yavaşlaması genelde Laravel'in içindeki değil, altındaki beş şeyden gelir: job başına açılan bağlantı, job içinde timeout'suz I/O, çekirdek sayısını yok sayan worker sayısı, memory leak'e karşı periyodik restart'ın olmayışı ve tek kuyruğa yığılmış öncelikler. Beşi de ölçülebilir; asıl mesele de bu: queue yavaş, üzerine iş yapacağınız bir hipotez olmamalı.

"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:

```php
'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çin `slow` kuyruğu, hızlı işler `default` kuyruğ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.

```ini
[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ın `ZCARD`'ı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ı.

## Sık sorulanlar

**Supervisor kaç queue worker çalıştırmalı?**

Bir worker tek bir PHP process, o da tek bir CPU core kullanıyor; dolayısıyla 4 core'lu bir makinede tek worker makinenin çoğunu atıl bırakıyor. Tipik kural CPU-bound işte numprocs = core sayısı, I/O-bound işte iki katı — sonra kurala güvenmek yerine o uygulamayı ölçün.

**Supervisor yapılandırmasında stopwaitsecs neden önemli?**

Supervisor bir süreci öldürmeden önce stopwaitsecs saniye bekliyor ve varsayılan 10. Uzun bir job'un ortasındaki worker bu sürede yarıda kesilir. Değeri en uzun job sürenizin üzerine çekin ki restart, job'un bitmesine izin versin.

**Kuyruğun gerçekten yavaş olduğunu hangi ölçümler söyler?**

Üç tanesi: kuyruk uzunluğu, job süresinin p95'i ve son beş dakikadaki failed job sayısı. Redis'te kuyruk uzunluğu, liste için LLEN artı delayed ve reserved sorted set'lerin ZCARD'ı demek; p95 ise kendi instrumentation'ınızı gerektiriyor — Horizon throughput ve bekleme süresi veriyor, yüzdelik dilim değil.


## Kaynaklar

- [Laravel queues: long-lived workers, --max-jobs, --max-time and queue priorities](https://laravel.com/docs/12.x/queues) — Laravel
- [Laravel Redis: persistent is a supported PhpRedis connection parameter](https://laravel.com/docs/12.x/redis) — Laravel
- [Supervisor program settings: numprocs and stopwaitsecs, which defaults to 10 seconds](https://supervisord.org/configuration.html) — Supervisor
- [Laravel Horizon: the metrics dashboard reports throughput and wait times](https://laravel.com/docs/12.x/horizon) — Laravel
