Teknoloji

PHP-FPM Havuzları: Session ve Kuyruk İşçisi Yönetimi

PHP-FPM Havuzları, Oturumlar ve Queue İşçileri

PHP ile yazılmış modern uygulamalarda (Laravel, Symfony, WooCommerce, özel paneller vb.) aynı sunucuda iki çok farklı iş yükünü bir arada koşturuyoruz: kullanıcı oturumlarını yöneten kısa ömürlü HTTP istekleri ve dakikalarca sürebilen, CPU ve I/O tüketen queue (kuyruk) işçileri. Örneğin bir kampanya sırasında kuyruk işçisi ve PHP-FPM süreci sayısı birlikte artırılırsa, ortak CPU ve RAM kapasitesi yetersiz kalabilir; frontend kullanıcıları yavaş yanıtlarla veya 502/504 hatalarıyla karşılaşabilir. Bu belirtiler tek başına nedeni kanıtlamaz, ancak ayrı süreçlerde çalışan iş yüklerinin aynı sunucu kaynakları için yarışması incelenmesi gereken olasılıklardan biridir.

Bu yazıda, izole havuz mimarisi için bir tasarım seçeneğini adım adım anlatacağız: PHP session ağırlıklı istekler için ayrı PHP-FPM havuzları, stateless API istekleri için ayrı havuzlar ve queue işçileri için Supervisor + systemd ile sınırlandırılmış bağımsız bir katman. Amaç, son kullanıcı yanıt sürelerini korurken kuyruk işçilerinin kaynak tüketimini ölçülmüş kapasiteye göre sınırlandırmak. Yol üzerinde session kilitlenmesi, FPM tıkanması, OOM killer ve CPU patlaması gibi klasik sorunlara da değineceğiz.

Buradaki izolasyon, önce süreç/eşzamanlılık ayrımıdır; tam güvenlik izolasyonu veya yanıt süresi garantisi değildir. FPM havuzları aynı master altında bellek/CPU rezervasyonu yapmaz. Gerçek kaynak sınırını ayrı servis/cgroup ile açıkça kuracağız.

Problemin Kökü: Aynı Kaynakları Paylaşan Farklı İş Yükleri

PHP-FPM kullanılan bir LEMP/LAMP kurulumunda sanal hostlar tek bir www havuzuna bağlanabilir. HTTP üzerinden yürütülen ağır işler bu havuzdaki kullanıcı istekleriyle yarışır; CLI kuyruk işçileri ise FPM dışında çalışır, fakat aynı sunucunun CPU ve RAM’ini paylaşır. Bu model küçük siteler için fena değildir ama trafik ve iş yükü büyüdüğünde aşağıdaki sorunlar görülebilir:

  • Session kilitlenmesi: Session handler’ı ve kilitleme ayarları buna yol açıyorsa, uzun süren bir isteğin oturum kilidini açık tutması aynı oturuma ait diğer istekleri bekletebilir. Dosya veya Redis depolaması seçimi tek başına kilitleme davranışını açıklamaz.
  • PHP-FPM saturasyonu: HTTP üzerinden yürütülen ağır raporlar FPM çocuk süreçlerini meşgul edebilir; yeni istekler bekleyebilir veya zaman aşımına uğrayabilir. CLI kuyruk işçileri FPM çocuklarını doldurmaz, fakat ortak kaynakları tüketerek HTTP yanıtlarını yavaşlatabilir.
  • Bellek baskısı: FPM süreçleri ve CLI işçileri aynı sunucunun RAM’ini tüketir. Büyük queue job’ları HTTP istekleri için kullanılabilecek belleği azaltabilir; ayrı süreçlerde çalışmak tek başına RAM rezervasyonu sağlamaz.
  • Farklı SLA’ler: Kullanıcı isteği için 300 ms hedeflerken, rapor üreten queue job’unuzun 120 sn çalışması gayet normal olabilir. Tek bir kaynak bütçesiyle ikisini dengelemek zordur.

Bu tabloya, yanlış seçilmiş session ve cache altyapısını da eklerseniz sorun katlanır. Session ve cache tarafındaki seçeneklerin performansa etkisini detaylı olarak anlattığımız PHP session ve cache depolamasını doğru seçme rehberimizi bu yazıdan sonra mutlaka okumanızı öneririz.

Mimari Yaklaşım: İzole Havuz ve İşçi Katmanları

Örnek mimari iki ana çalışma katmanından oluşur:

  • 1) PHP-FPM havuz katmanı: HTTP isteklerini karşılayan, Nginx/Apache arkasında çalışan ve istekler arasında yeniden kullanılan süreçler. Burada da kendi içinde session ağırlıklı ve stateless havuzlar açıyoruz.
  • 2) Queue işçi katmanı: php-cli ile çalışan, Supervisor ve systemd tarafından yönetilen, FPM dışında çalışan işçiler. Bu katman için CPU ve RAM sınırları ayrıca yapılandırılır; sunucunun fiziksel kaynakları yine ortaktır.

Bu tasarımın üç temel hedefi var:

  • Ön yüzdeki kullanıcı isteklerini, arka plandaki batch işlerden izole etmek.
  • Uzun süren işleri HTTP katmanından ayırarak session kullanan istekler üzerindeki kaynak baskısını azaltmak. Havuz ayrımı tek başına oturum kilitlenmesini çözmez.
  • FPM havuzları ile kuyruk işçileri için CPU, RAM ve I/O bütçeleri belirlemek; havuz ayrımını gerektiğinde işletim sistemi düzeyindeki kaynak sınırlarıyla tamamlamak.

Kuyruk ve arka plan işleri tarafının detaylı genel resmini merak ediyorsanız, adım adım örneklerle anlattığımız VPS üzerinde arka plan işleri ve kuyruk yönetimi yazımız bu makalenin doğal tamamlayıcısıdır.

PHP-FPM ile Session Ağırlıklı ve Stateless Havuzları Ayırmak

Önce HTTP tarafını düzenleyelim. Hedefimiz en az iki ayrı FPM havuzu:

  • session_pool: Kullanıcı oturumu yöneten frontend ve admin istekleri. Örneğin WordPress, WooCommerce, geleneksel paneller. Burada kullanıcı oturumu kavramını PHP’nin yerleşik session mekanizmasıyla eşitlememek gerekir; aşağıdaki PHP session ayarları ancak uygulama bu mekanizmayı kullanıyorsa ilgilidir.
  • api_pool: JWT veya token tabanlı, mümkün olduğunca stateless API istekleri.

Bu sayede, ölçülen kaynak tüketimi izin veriyorsa API tarafında daha yüksek concurrency kullanabilir, session havuzunu farklı sınırlarla çalıştırabiliriz. Havuzun stateless olması ise uygulamanın oturum kullanımına bağlıdır; yalnızca ayrı bir havuz tanımlamak bunu sağlamaz.

Örnek PHP-FPM Havuz Konfigürasyonu

Örnek Ubuntu 24.04, PHP 8.3 FPM/CLI, Nginx, systemd ve Supervisor 4.2+ içindir. Ayrı bir test sunucusunda, çalışan ve yedeği bulunan bir uygulama üzerinden ilerleyin. /var/www/site/public/index.php yalnız ön denetleyici girişidir; uygulama bağımlılıkları, iki alan adını kapsayan mevcut TLS sertifikası, veritabanı, sırlar ve dağıtım hesabı bu rehberde kurulmaz. WordPress’in wp-login.php gibi ayrı giriş dosyaları vardır: bu index.php-only örneğini WordPress yapılandırmasına doğrudan kopyalamayın.

Yeni app grubunu, appweb ve appqueue nologin hesaplarını yönetici olarak oluşturun; mevcut adları yeniden kullanmadan önce yetkilerini inceleyin. Uygulama kodu bu çalışma hesaplarınca değiştirilememeli; gerekli storage/bootstrap/cache alanlarının erişimi dağıtımınıza göre verilmelidir. appqueue yalnız gerçek işlerin gerektirdiği dosya ve veritabanı haklarını alır; adı kendiliğinden daha az ayrıcalık sağlamaz. Nginx’in www-data kimliği, üst dizinlerden geçip yalnız public dosyalarını okuyabilmelidir. Ortak uygulama sırları aynı güven sınırında kalır.

Örneğe özel /etc/php/8.3/fpm-app.d, /var/log/php-fpm-app, /etc/supervisor-app-queue/programs ve /var/log/app-queue dizinlerini root sahipliğinde oluşturun. Yalnız appweb erişimli /var/lib/app-sessions dizini için 0700 kullanın. İki havuzun .error.log dosyalarını appweb:app, 0640 olarak hazırlayın; log döndürmenin sahiplik ve yeniden açma davranışını da tanımlayın. Sudo/root ile çalışan servis dosyaları, ayarlar ve üst dizinler geliştirici/çalışma hesaplarının yazmasına kapalı olmalıdır.

Aşağıdaki master yalnız bu uygulamaya ait iki havuzu yükler. Bu include dizinine paketle gelen www havuzunu veya başka havuzları eklemeyin. Başka çalışan FPM master/havuzları da sunucunun kapasite hesabına dahildir; yeni servis onları durdurmaz. Aynı uygulamayı eski/yeni servislerden yanlışlıkla çift çalıştırmadan, Nginx eşlemesini bakım ve geri dönüş planıyla değiştirin. Havuzlar yalnız eşzamanlı istek sınırlarını ayırır; aynı master içindeki OPcache ortaktır ve havuzlar tam güvenlik sınırı değildir.

[global]
pid = /run/php-fpm-app/master.pid
error_log = /var/log/php-fpm-app/master.log
daemonize = no
include = /etc/php/8.3/fpm-app.d/*.conf

Dosyayı /etc/php/8.3/fpm-app.conf, aşağıdaki servisi /etc/systemd/system/php-fpm-app.service olarak hazırlayın. 4 GiB sınır iki havuzun master/çocuk süreçlerinin toplu bütçesidir; her havuza ayrı RAM rezervasyonu vermez. MemoryMax aşımı HTTP işlerini de sonlandırabilir; bu örnek değerleri kabul testi olmadan kapasite önerisi saymayın.

[Unit]
Description=Dedicated FPM master for the example application
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
RuntimeDirectory=php-fpm-app
RuntimeDirectoryMode=0755
ExecStart=/usr/sbin/php-fpm8.3 --nodaemonize --fpm-config /etc/php/8.3/fpm-app.conf
ExecReload=/bin/kill -USR2 $MAINPID
KillSignal=SIGQUIT
KillMode=mixed
TimeoutStopSec=75s
Restart=on-failure
RestartSec=5s
MemoryHigh=3G
MemoryMax=4G
MemorySwapMax=0
CPUAccounting=yes
MemoryAccounting=yes
IOAccounting=yes

[Install]
WantedBy=multi-user.target

Aşağıdaki iki havuz dosyasını yalnız fpm-app.d dizinine kaydedin. 12/8 çocuk, 60/15 s tavanlar ve 128 MiB PHP tahsis limiti örnek başlangıç değerleridir; uygulamanın gereksinimini ve eşzamanlı tepe kullanımını ölçmeden kullanmayın.

[session_pool]
user = appweb
group = app
listen = /run/php-fpm-app/session_pool.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 12
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 1000
request_terminate_timeout = 60s
request_terminate_timeout_track_finished = yes
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm-app/session_pool.slow.log
catch_workers_output = yes
clear_env = yes
security.limit_extensions = .php
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30
php_admin_value[session.save_handler] = files
php_admin_value[session.save_path] = /var/lib/app-sessions
php_admin_value[session.gc_maxlifetime] = 1440
php_admin_value[session.gc_probability] = 1
php_admin_value[session.gc_divisor] = 100
php_admin_value[session.auto_start] = 0
php_admin_value[session.use_strict_mode] = 1
php_admin_value[session.use_only_cookies] = 1
php_admin_value[session.cookie_httponly] = 1
php_admin_value[session.cookie_secure] = 1
php_admin_value[session.cookie_samesite] = Lax
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm-app/session_pool.error.log
[api_pool]
user = appweb
group = app
listen = /run/php-fpm-app/api_pool.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 1000
request_terminate_timeout = 15s
request_terminate_timeout_track_finished = yes
request_slowlog_timeout = 2s
slowlog = /var/log/php-fpm-app/api_pool.slow.log
catch_workers_output = yes
clear_env = yes
security.limit_extensions = .php
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30
php_admin_value[session.save_handler] = files
php_admin_value[session.save_path] = /var/lib/app-sessions
php_admin_value[session.gc_maxlifetime] = 1440
php_admin_value[session.gc_probability] = 1
php_admin_value[session.gc_divisor] = 100
php_admin_value[session.auto_start] = 0
php_admin_value[session.use_strict_mode] = 1
php_admin_value[session.use_only_cookies] = 1
php_admin_value[session.cookie_httponly] = 1
php_admin_value[session.cookie_secure] = 1
php_admin_value[session.cookie_samesite] = Lax
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm-app/api_pool.error.log

Buradaki bazı kritik noktalar:

  • pm.max_children: aynı anda karşılanan isteklerin havuz bazındaki sınırıdır; RAM veya CPU rezervasyonu değildir. WordPress/WooCommerce PHP-FPM ayarları kapasite hesabının ilgili bağlamını ele alır.
  • request_terminate_timeout: worker’ı sonlandırır; bitmiş yan etkileri geri almaz. track_finished=yes, fastcgi_finish_request/kapama aşamasında da sınırı uygular. PHP max_execution_time ile aynı mekanizma değildir; I/O sayımı sürüm/derleme/platforma bağlıdır.
  • Session: iki havuz aynı uygulamanın yerleşik dosya session deposunu kullanır. API’nin stateless olması session.auto_start=0 ayarından değil, uygulama middleware/oturum davranışından gelir.

Nginx ile Havuzları Sanal Host’lara Bağlamak

Bu, Nginx’in http bağlamından include edilen tam site eşleme örneğidir. www.example.com session_pool’a, api.example.com api_pool’a gider. Sertifika iki adı da kapsamalı; sertifika/anahtar dosyaları önceden mevcut olmalıdır. Çerezler Secure olduğu için istemci erişimi HTTPS’tir. Aşağıdaki FastCGI dosyasını /etc/nginx/snippets/front-controller-fastcgi.conf olarak kaydedin; dağıtımın farklı fastcgi_params dosyasını ayrıca eklemeyin.

fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param PATH_INFO "";
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param HTTP_HOST $http_host;
fastcgi_param HTTP_PROXY "";
fastcgi_param REDIRECT_STATUS 200;
upstream php_session_pool {
    server unix:/run/php-fpm-app/session_pool.sock;
}
upstream php_api_pool {
    server unix:/run/php-fpm-app/api_pool.sock;
}

server {
    listen 443 ssl;
    server_name www.example.com;
    root /var/www/site/public;
    index index.php;
    ssl_certificate /etc/ssl/app/fullchain.pem;
    ssl_certificate_key /etc/ssl/app/privkey.pem;
    access_log /var/log/nginx/www.example.com.access.log;
    error_log /var/log/nginx/www.example.com.error.log;
    client_max_body_size 10m;

    location = /index.php {
        try_files /index.php =404;
        include /etc/nginx/snippets/front-controller-fastcgi.conf;
        fastcgi_pass php_session_pool;
        fastcgi_connect_timeout 3s;
        fastcgi_read_timeout 65s;
    }
    location ~ /\. { return 404; }
    location ~* \.php(?:/|$) { return 404; }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

server {
    listen 443 ssl;
    server_name api.example.com;
    root /var/www/site/public;
    index index.php;
    ssl_certificate /etc/ssl/app/fullchain.pem;
    ssl_certificate_key /etc/ssl/app/privkey.pem;
    access_log /var/log/nginx/api.example.com.access.log;
    error_log /var/log/nginx/api.example.com.error.log;
    client_max_body_size 10m;

    location = /index.php {
        try_files /index.php =404;
        include /etc/nginx/snippets/front-controller-fastcgi.conf;
        fastcgi_pass php_api_pool;
        fastcgi_connect_timeout 3s;
        fastcgi_read_timeout 20s;
    }
    location ~ /\. { return 404; }
    location ~* \.php(?:/|$) { return 404; }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

Yalnız mevcut public/index.php çalıştırılır. Başka .php dosyaları, PATH_INFO biçimindeki /index.php/ek ve nokta dizinleri reddedilir; yükleme dizinindeki bir PHP dosyası çalıştırılmaz. REQUEST_URI, sorgu ve POST parametreleri korunur. İndex dışındaki girişleri olan uygulamalarda gerçek dosya sözleşmesini ayrıca tasarlayın. fastcgi_read_timeout iki okuma arasındaki süreyi sınırlar; toplam istek süresi garantisi değildir.

Tek alan adında API ayrımı gerekiyorsa, yalnız www server bloğuna şu iki location eklenebilir. /api ve /api/... API havuzuna, /apix normal havuza gider. /api/ sıradan bir prefix’tir; ^~ kullanmayın, çünkü üstteki PHP/nokta-yol ret kurallarının atlanmasını istemiyoruz. Backend SCRIPT_FILENAME her iki durumda da sabit index.php’dir; uygulama yönlendirme yolunu REQUEST_URI’den alır.

location = /api {
    try_files /index.php =404;
    include /etc/nginx/snippets/front-controller-fastcgi.conf;
    fastcgi_pass php_api_pool;
    fastcgi_connect_timeout 3s;
    fastcgi_read_timeout 20s;
}
location /api/ {
    try_files /index.php =404;
    include /etc/nginx/snippets/front-controller-fastcgi.conf;
    fastcgi_pass php_api_pool;
    fastcgi_connect_timeout 3s;
    fastcgi_read_timeout 20s;
}

Yerleşik PHP session kullanan aynı uygulama havuzları aynı erişilebilir depolamayı, session adını, çerez kapsamını, yaşam süresini ve GC politikasını kullanmalıdır. Aksi halde login/sepet akışı havuz geçişinde kaybolabilir. Alt alan adları host-only çerezleri kendiliğinden paylaşmaz; cookie Domain politikasını güven sınırına göre ayrıca belirleyin. WordPress/WooCommerce ve Laravel’in kendi oturum katmanları, bu php.ini ayarlarıyla otomatik olarak değiştirilmez. Laravel varsayılan olarak aynı session’a ait eşzamanlı isteklere izin verir; route blocking ayrıca atomik kilit destekli sürücü ve doğru TTL/wait ile yapılandırılır.

Dosya handler’ının kilidini gereğinden uzun tutmayın. Aşağıdaki çalışabilir yerleşik PHP örneği durumu yazar ve yavaş alıştırma adımından önce kilidi bırakır. session_write_close sonrasında $_SESSION üzerinde yapılan yeni değişiklikler kendiliğinden kaydedilmez. Eşzamanlı güncellemelerin uygulama düzeyinde tutarlılığı ayrıca tasarlanmalıdır; kilidi erken bırakmak her iş akışında güvenli değildir.

<?php
declare(strict_types=1);

if (!session_start()) {
    http_response_code(503);
    exit('Session unavailable');
}
$_SESSION['visits'] = (int) ($_SESSION['visits'] ?? 0) + 1;
$visits = $_SESSION['visits'];
if (!session_write_close()) {
    http_response_code(503);
    exit('Session save failed');
}
// Perform slow work only after saving and releasing the native session lock.
// This synthetic pause is not an application background queue.
usleep(200000);
header('Content-Type: application/json');
echo json_encode(['visits' => $visits], JSON_THROW_ON_ERROR);

Redis anlamlı bir alternatiftir; daha hızlı veya kilitsiz olacağı garanti değildir. PhpRedis 6.2.0 belgesinde session locking varsayılan olarak kapalıdır ve tek-master topolojisi için desteklenir. Aşağıdaki opsiyonel kesit yalnız özel yerel alıştırma Redis’i için gösterilir; üretimde doğru ACL/kimlik bilgisi, gerektiğinde TLS, erişim ve veri saklama politikasını ayrıca sağlayın. Aynı uygulamaya hizmet veren iki havuza tutarlı uygulanır; birbirinden bağımsız uygulamalar aynı anahtar alanını paylaşmaz.

Bu kesit PHP INI sözdizimindedir. Redis seçeneği için önce PhpRedis uzantısının FPM bağlamında yüklü olduğunu doğrulayın. İki havuzda da mevcut php_admin_value[session.save_handler] değerini redis, php_admin_value[session.save_path] değerini kesitteki aynı Redis bağlantı adresi yapın; havuzdaki files ve dosya yolu ayarlarını bırakıp yalnız genel php.ini dosyasını değiştirmek yeterli değildir. Kilitleme ayarlarını FPM’in yüklediği INI dosyasında uygulayın. Kontrollü yeniden yüklemeden sonra etkin handler/depolama ayarlarını ve havuzlar arası oturum sürekliliğini doğrulayın; mevcut dosya oturumları Redis’e kendiliğinden taşınmaz, geçiş veya yeniden oturum açma planı gerekir.

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?database=2&prefix=shop_session_&timeout=1&read_timeout=1"
redis.session.locking_enabled = 1
redis.session.lock_expire = 90
redis.session.lock_wait_time = 10000
redis.session.lock_retries = 100
session.gc_maxlifetime = 1440

lock_expire saniye, lock_wait_time mikrosaniyedir. Örnekte kilit ömrü 90 s, FPM’in en uzun 60 s işleyiş tavanından uzundur; 100 × 10000 µs yaklaşık 1 s yeniden deneme beklemesidir ve ağ sürelerini içermez. Kilit alınamazsa session_start hatasını başarısız istek olarak yönetin. Uzun bloklayan I/O, kapatma adımları veya değişen timeout’lar için ilişkiyi yeniden test edin. Redis database/prefix yalnız veri ayrımıdır; bağımsız RAM/CPU veya yetki sınırı değildir. RedisCluster/RedisArray için bu kilit örneğini varsaymayın.

Bu ayrımın getirileri:

  • Uzun süren API istekleri frontend havuzunun çocuk süreçlerini doğrudan meşgul etmez; ortak CPU, RAM ve veritabanı üzerindeki rekabet ise devam edebilir.
  • API tarafında concurrency ve timeout değerlerini frontend’den bağımsız, ölçülen yük ve beklenen istek sürelerine göre ayarlayabilirsiniz.
  • Örnekte slow log’lar havuz bazında ayrıldığı için yavaş isteklerin hangi havuzda çalıştığını görmek kolaylaşır. Örnekte PHP hata log’ları da havuz başına ayrı dosyaya yazılır; master ve worker stdout/stderr kayıtlarının kapsamı farklıdır.

Queue İşçilerini Kaynak Sınırlarıyla Ayırmak: Supervisor + systemd

CLI kuyruk işçileri FPM dışında çalışır; aynı CPU/RAM/depolama/veritabanı kaynakları için rekabet edebilir. Aşağıdaki cgroup tasarımı bu katmanı ölçülebilir bir bütçeye bağlar; FPM yanıt süresini veya host kullanılabilirliğini tek başına garanti etmez.

  • CLI işçilerinin hangi servis/cgroup içinde olduğunu belirleyin.
  • Supervisor seçildiyse yalnız bu işçilere ait bağımsız Supervisor çalıştırın.
  • İşçi sayısı, uygulama limitleri ve toplu cgroup bütçesini birlikte ölçün.

Supervisor Konfigürasyonu ile Queue İşçileri

Aşağıdaki iki süreç yöneticisi alternatiftir: aynı işleri hem Supervisor hem doğrudan systemd ile başlatmayın. Çalışan Laravel uygulaması, PHP CLI uzantıları (timeout için PCNTL dahil), mevcut database queue tabloları/bağlantısı ve en az ayrıcalıklı iş kimliği önkoşuldur. Gerçek config/queue.php database bağlantısında retry_after=90 belirleyin; kuyruk adı default. Job sınıfında 60 s’yi aşan override olmamalı. 60 s worker timeout <75 s graceful stop <90 s retry_after ilişkisini gerçek iş/I/O sürelerine göre uyarlayın. SQS kullanıyorsanız retry_after yerine görünürlük zaman aşımı geçerlidir.

CLI, FPM havuz INI ayarlarını devralmaz; php8.3 --ini ve ilgili uzantıları CLI bağlamında kontrol edin. PHP memory_limit uygulama tahsis sınırıdır, toplam RSS tavanı değildir; PHP 8.3.36 ve ilgili güncel dallarda yumuşak tahsis sınırı olarak kısa süre aşılabilir. Laravel –memory işçi geri dönüş eşiği, cgroup MemoryMax ise toplu OS sınırıdır. Bunlar aynı ölçüm veya garanti değildir. Uygulama/dış servis timeout’larını, idempotent işleri ve tekrar/başarısızlık politikasını ayrıca kurun.

Supervisor’ı mevcut genel /etc/supervisor/supervisord.conf üzerinden ikinci kez çalıştırmayın. Yalnız bu uygulamaya ait programları include eden ayrı dosya kullanın: /etc/supervisor-app-queue/supervisord.conf. Unix kontrol soketi ve PID dosyası da ayrıdır; TCP kontrol arayüzü açılmaz.

[unix_http_server]
file=/run/app-queue-supervisor/control.sock
chmod=0700

[supervisord]
nodaemon=true
pidfile=/run/app-queue-supervisor/supervisord.pid
logfile=/var/log/app-queue/supervisord.log
logfile_maxbytes=10MB
logfile_backups=3
childlogdir=/var/log/app-queue

[rpcinterface:supervisor]
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///run/app-queue-supervisor/control.sock

[include]
files=/etc/supervisor-app-queue/programs/*.conf

Aşağıdakini /etc/supervisor-app-queue/programs/queue.conf olarak kaydedin. Komut tek satırdır. Dört süreçte ayrı dönen log dosyaları kullanılır; aynı dönen dosyayı paylaşmak bozulmaya yol açabilir.

[program:app-queue]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php8.3 -d memory_limit=384M /var/www/site/artisan queue:work database --queue=default --sleep=1 --tries=3 --timeout=60 --memory=256 --max-time=3600
directory=/var/www/site
numprocs=4
user=appqueue
autostart=true
autorestart=true
startsecs=1
startretries=3
stopsignal=TERM
stopwaitsecs=75
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/log/app-queue/worker_%(process_num)02d.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3

Buradaki kritik noktalar:

  • numprocs=4: başlangıç örneğidir; 4 vCPU var diye 4 CPU-ağır işçinin uygun olduğu sonucu çıkmaz.
  • 384M / –memory=256: PHP tahsis sınırı ve Laravel’in işçi geri dönüş eşiği farklıdır. Ağır bir iş sınırı aşabilir; OOM veya timeout yarım kalmış dış yan etkiler bırakabilir.
  • Log ve yaşam döngüsü: her işçinin ayrı dönen logu ve graceful stop süresi vardır; sırları loglamayın, saklama ve erişimi sınırlayın.

Laravel tarafında queue ve Horizon optimizasyonlarını daha derinlemesine incelemek isterseniz, Laravel prod ortam optimizasyonu rehberimizde gerçek dünya ayar örnekleri bulabilirsiniz.

Supervisor’ı systemd ile İzole Etmek

Aşağıdaki systemd birimini /etc/systemd/system/app-queue-supervisor.service olarak kaydedin ve yalnız bu Supervisor için kullanın; root master’ın başlattığı gerçek job süreçleri programdaki appqueue kimliğine geçer. Yetki dosyaları güvenilir olmalı; Supervisor genel host yöneticisini bu birime taşımayın.

[Unit]
Description=Dedicated Supervisor for application queue workers
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
RuntimeDirectory=app-queue-supervisor
RuntimeDirectoryMode=0700
ExecStart=/usr/bin/supervisord -n -c /etc/supervisor-app-queue/supervisord.conf
Restart=on-failure
RestartSec=5s
KillMode=mixed
TimeoutStopSec=90s
CPUAccounting=yes
CPUQuota=150%
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
IOAccounting=yes
IOWeight=50

[Install]
WantedBy=multi-user.target

Açıklamalar:

Dört CLI işçi ve onları yöneten Supervisor aynı cgroup bütçesindedir. CPUQuota=150% tek CPU’nun 1,5 katı CPU zamanıdır; 4 vCPU sunucuda toplamın yaklaşık %37,5’i, FPM için CPU rezervasyonu değildir. Yüzde işareti burada tektir. MemoryHigh=1536M baskıda yavaşlatır, MemoryMax=2G son savunma olarak cgroup içinde OOM’a yol açabilir; bütün sunucuyu OOM’dan koruma garantisi değildir. Swap örnekte ayrıca 0 ile sınırlandırılır. IOWeight=50 varsayılan 100’e göre göreli ağırlıktır; daha yüksek ağırlık kuyruğu geri plana atmaz, etki etkin controller/scheduler/hiyerarşiye bağlıdır.

KillMode=mixed önce Supervisor’a TERM gönderir; Supervisor işçilere grup sinyali ve 75 s bekleme uygular. Dış 90 s stop penceresi buna pay bırakır; son aşamada kalan süreçler zorla öldürülebilir. Bir işin tamamlanması veya exactly-once sonuç garanti edilmez. Sık başarısız başlangıçlar StartLimit sınırına takılır; neden giderilmeden sonsuz yeniden başlatma beklemeyin.

Toplam host kapasitesini seçerken VPS kaynaklarını uygulamanın ölçülmüş tepe yükü ve diğer servisleriyle birlikte değerlendirin.

Ayarları ve uygulama bağlantısını izole test ortamında doğruladıktan sonra yönetici olarak yalnız seçilen alternatifi başlatın. Aşağıdakiler gerçek servis işlemleridir; mevcut canlı işlerin sahipliği, bakım ve geri dönüş planı ayrıca gerekir:

systemd-analyze verify /etc/systemd/system/app-queue-supervisor.service
systemctl daemon-reload
systemctl enable --now app-queue-supervisor.service
supervisorctl -c /etc/supervisor-app-queue/supervisord.conf status

Böylece queue tarafı, PHP-FPM’den hem proses hem de cgroup düzeyinde ayrılmış olur. RAM ve OOM killer davranışının sistem genelinde nasıl işlediğini daha detaylı anlamak isterseniz, VPS’te RAM, swap ve OOM killer yönetimi yazımız tam bu noktada çok faydalı.

Supervisor Yerine Doğrudan systemd Kullanmak

Supervisor bağımlılığını istemiyorsanız doğrudan systemd de aynı CLI işçi rolünü yönetebilir. Bu bir alternatif seçimdir.

Alternatif olarak Supervisor’ı kullanmadan, önce ortak /etc/systemd/system/app-queue.slice ve sonra /etc/systemd/system/app-queue@.service dosyasını kurun. Bütün instance’lar aynı 2 GiB/150% toplam sınıra dahildir; instance başına 512 MiB sınır toplam bütçeyi dört ayrı 2 GiB’ye dönüştürmez. Instance numarası süreç kimliğidir, kuyruk adı değildir: hepsi açıkça default kuyruğunu dinler.

[Unit]
Description=Aggregate resource budget for application queue workers

[Slice]
CPUAccounting=yes
CPUQuota=150%
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
IOAccounting=yes
IOWeight=50
[Unit]
Description=Application queue worker instance %i
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
User=appqueue
Group=app
WorkingDirectory=/var/www/site
ExecStart=/usr/bin/php8.3 -d memory_limit=384M /var/www/site/artisan queue:work database --queue=default --sleep=1 --tries=3 --timeout=60 --memory=256 --max-time=3600
Restart=always
RestartSec=5s
SuccessExitStatus=0
KillSignal=SIGTERM
KillMode=mixed
TimeoutStopSec=75s
Slice=app-queue.slice
MemoryMax=512M
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Restart=always, –max-time veya queue:restart nedeniyle oluşan normal çıkışları da yeniden başlatır; systemctl stop ile bilerek durdurulan birimi otomatik başlatmaz. Job timeout, CLI ini/PCNTL ve graceful-stop önkoşulları yine geçerlidir. Ortak slice hiyerarşisi ve aktif controller’lar gerçek sistemde doğrulanmalıdır.

Ardından örneğin 4 işçi başlatmak için:

systemd-analyze verify /etc/systemd/system/app-queue@.service /etc/systemd/system/app-queue.slice
systemctl daemon-reload
systemctl enable --now app-queue@1 app-queue@2 app-queue@3 app-queue@4

Her işçi ayrı servis olarak, logları journal üzerinden izlenir. Yalnız seçtiğiniz yöneticiyi çalıştırın. Esas amaç CLI işleri ile HTTP kapasitesini ve onların toplam kaynak bütçelerini açıkça ayırmak; aynı makinedeki ortak bağımlılıklar yine incelenmelidir.

Kaynak Planlama: Kaç FPM Süreci, Kaç Queue İşçisi?

Örnek hesabımız 4 vCPU/8 GiB host içindir: bu FPM master’ına 4 GiB, queue katmanına 2 GiB ve OS/Nginx/veritabanı/Redis/diğer paylaşımlar için toplam 2 GiB taslak bütçe. Son bölüm gerçek servisleriniz için yetersiz olabilir; toplam talepler, page cache ve emniyet payı ölçülmeden planı kabul etmeyin. Limitlerin toplamı fiziksel belleğe eşit diye güvenli kapasite kanıtlanmaz.

Adım 1: PHP-FPM Havuz RAM Hesabı

FPM süreçlerini bütün sistem bağlamında listeleyin. RSS birimi KiB’dir; paylaşılan sayfaları süreçler arasında tekrar sayabilir. PSS/USS, cgroup MemoryCurrent ve yoğun dönem tepelerini de kullanın. Yalnız boşta ortalama veya ilk 10 süreç üzerinden karar vermeyin.

Doğrudan systemd işçilerini seçtiyseniz Supervisor birimi yerine app-queue.slice ve ilgili app-queue@ instance’larını inceleyin.

ps -eo pid,user,rss,args --sort=-rss | awk '/php-fpm: pool/ && !/awk/ {print}'
systemctl show php-fpm-app.service -p MemoryCurrent -p MemoryHigh -p MemoryMax -p ControlGroup
systemctl show app-queue-supervisor.service -p CPUQuotaPerSecUSec -p MemoryCurrent -p MemoryMax -p ControlGroup

Yalnız aritmetiği göstermek için session sürecini 80 MiB, API sürecini 60 MiB varsayalım. 4 GiB FPM bütçesini iki 2 GiB parçaya bölmek gerçek per-pool rezervasyon değildir:

  • 2048MiB / 80MiB ≈25 ve 2048MiB / 60MiB ≈34, master/OPcache/uzantı ve dalgalanma payı içermeyen teorik bölmelerdir; güvenli max_children önerisi değildir.
  • Bu rehber 12/8 çocukla sınırlı örnek başlatır. Gerçek isteğin tepe maliyeti, CPU, veritabanı bağlantı sayısı ve gecikme hedefiyle azaltın/artırın. 128 MiB PHP ayarı toplam süreç RSS sınırı değildir.

Adım 2: Queue İşçisi RAM Hesabı

Queue için bir işçinin 350 MiB ölçülmüş tepe kullandığını varsayarsak:

  • 2048MiB / 350MiB ≈5, Supervisor/diğer giderler ve güvenlik payı yokken teorik üst bölmedir. Örnekte 4 işçi seçilmiştir; gerçek iş yükü için bu bile yüksek olabilir.
  • MemoryMax=2G cgroup baskısını sınırlar ve grup içinde OOM’a yol açabilir; global OOM’u imkânsız yapmaz. Job idempotency, timeout, yeniden deneme ve başarısız iş izleme gerekir.

Bu kaba hesapları ancak yük testiyle doğrulayın. VPS kaynak kullanımı izleme rehberi ile CPU throttling, bellek/PSS, I/O, queue yaşı, veritabanı bağlantıları ve HTTP p95/p99 değerlerini birlikte inceleyin.

Örnek Senaryolar: WooCommerce, Laravel API ve Raporlama

Aşağıdaki senaryolar tasarım seçenekleridir; ölçülmüş bir müşteri sonucu veya hız garantisi değildir.

1. WooCommerce + Promosyon Kampanyası

WooCommerce kampanyasında sepet/checkout istekleri ile PDF/stok işlerinin aynı kaynakları tükettiğini varsayalım. Kullanılan eklentinin arka plan işi HTTP, cron veya CLI olabilir; gerçek yürütme biçimini önce belirleyin.

  • HTTP tetiklenen ağır işler FPM çocuklarını kullanabilir; CLI işler FPM çocuklarını doldurmaz ama ortak kaynağı tüketir.
  • WooCommerce oturumu, bir eklenti yerleşik PHP session kullanmadıkça session_start dosya kilidiyle eş anlamlı değildir. Gerçek handler ve darboğaz ölçülür.
  • Büyüyen eşzamanlılık veritabanı bağlantı/lock/I/O baskısını artırabilir.

İzole havuz mimarisinde ise:

  • Frontend havuzunun concurrency ve timeout’larını gerçek checkout süresine göre seçin.
  • API/admin’i ayrı havuza almak gerekiyorsa uygulamanın gerçek giriş dosyalarını ve session tutarlılığını koruyun; index.php-only örneği WordPress’in tüm girişlerini kapsamaz.
  • Queue katmanını sınırlayın; kuyruk patlamasında login’in her zaman çalışacağını varsaymayın, ortak DB/Redis/host kapasitesini de test edin.

2. Laravel API + Mobil Uygulama

Mobil API token/JWT kullanıyor olsa bile middleware session açabilir. Stateless tasarımı uygulama üzerinden doğruladıktan sonra api_pool eşlemesini değerlendirin:

  • Gereksiz session başlatılmasını uygulamada kaldırın.
  • Timeout ve concurrency’yi endpoint süreleri ve kapasite testine göre seçin; kısa timeout bütün endpoint’ler için uygun değildir.
  • Queue yükü altında API p95/p99, hata oranı ve ortak DB/CPU baskısını ölçün; havuz adı gecikme garantisi vermez.

Bu senaryoda, Laravel’in HTTP ve queue katmanlarının birlikte nasıl optimize edileceğini merak ediyorsanız, Laravel prod ortam optimizasyonu yazımızda Octane, Redis ve Horizon ile bir üst seviyeye geçişi de detaylandırdık.

3. Ağır Raporlama ve Batch İşleri

Gece raporları, PDF üretimi ve entegrasyonların süre hedefleri kullanıcı isteklerinden farklı olabilir. İşletme gecikme hedefini belirleyin; gece de çalışan kullanıcı ve paylaşılan servisleri hesaba katın.

Bu durumda şu seçenekleri değerlendirin:

  • Rapor işleri için örneğin reports adlı kuyruk tanımlayın ve o kuyruğu dinleyen ayrı program/komut kullanın.
  • İlgili işçilerin ayrı cgroup/slice bütçesini gerçek CPU/I/O ihtiyacına göre kurun; IOWeight göreli önceliktir.
  • İşçi sayısını zamanlayarak değiştiriyorsanız concurrency, tek-yönetici sahipliği ve geri dönüşü koruyun. Bu değişiklikler için cron/systemd timer ayrı operasyonel tasarımdır.

Loglama, İzlenebilirlik ve Sorun Giderme

Havuz ve işçi katmanlarını ayırırken, ölçümleri de hangi role ait oldukları anlaşılacak biçimde düzenleyin:

  • FPM: ayrı slow/error log, aktif/boş çocuklar, listen queue, max children reached ve yavaş istekler. Status sayfasını yalnız yetkili erişime açın.
  • Queue: iş yaşı/backlog, süre/başarı/tekrar/failed job, süreç çıkışları ve ayrı loglar.
  • Host: CPU throttling, cgroup memory.events/MemoryCurrent, swap/I/O ve DB/Redis metrikleri. FPM status tek başına session-lock teşhisi koymaz; stack/log/zaman korelasyonu gerekir.

Havuz ve işçi bazında ölçüm, hangi katmanın baskı altında olduğunu ayırmaya yardımcı olur. Tek bir metrikten kök neden çıkarmayın; örneğin queue MemoryMax olayı ile aynı anda görülen HTTP yavaşlığı ortak veritabanından da kaynaklanabilir.

DCHost Altyapısında İzole Havuz Mimarisi Nasıl Konumlanır?

Bu model bir dedicated sunucuda veya uygun VPS’te uygulanabilir. NVMe depolama, tek başına düşük I/O gecikmesi ya da iyi izolasyon garantisi değildir. İhtiyaca göre şu bileşenleri değerlendirin:

  • HTTP rolüne göre ölçülmüş FPM havuzları.
  • Ayrı toplam kaynak bütçesinde CLI kuyruk süreçleri.
  • Uygulama ihtiyaçlarına göre dosya/Redis gibi session/cache seçenekleri.
  • Gerekliyse ayrı veritabanı veya işçi host’u.

Veritabanını ayırmak ağ, erişim, hata ve tutarlılık sınırlarını da değiştirir. Veritabanı sunucusunu uygulamadan ayırma rehberi kararın diğer yönlerini ele alır.

Geçişi aşamalı hazırlayın: mevcut yapılandırma/kimlik/soket/işçi envanteri, geri yüklenebilir yedek, ayrı test ortamı ve geri dönüş adımları. Test kopyasının e-posta/ödeme/webhook gibi gerçek dış etkilerini kapatın. Önce yönlendirme/session doğruluğu, ardından karışık HTTP+queue yük testi ve kontrollü kesim yapın.

Özet ve Sonraki Adımlar

PHP performansını kod, veri katmanı, süreç sayıları ve kaynak paylaşımı birlikte belirler. Ayrı FPM havuzları ile bağımsız CLI queue süreçleri bazı darboğazları yönetmeyi kolaylaştırır; tam izolasyon veya kesintisizlik sağlamaz.

  • HTTP havuzlarının concurrency/timeout ve aynı uygulamanın session tutarlılığı birlikte ele alındı.
  • Queue için iki alternatif süreç yöneticisi, tek toplam cgroup bütçesi ve kontrollü işçi yaşam döngüsü verildi.
  • Kapasite değerlerinin varsayımsal olduğu; kaynak/uygulama kabul testleri ve geri dönüş gerektirdiği açıklandı.

Kendi uygulamanızda bu rollerin hangi süreçlerde çalıştığını ve hangi kaynakları paylaştığını belgeleyerek başlayın. FPM yapılandırmasını php-fpm8.3 --test --fpm-config /etc/php/8.3/fpm-app.conf, Nginx’i nginx -t ile denetleyin; bu kontroller yük veya session kabul testi değildir. Gerçek yönlendirme, login/sepet, queue yeniden deneme ve durdurma testlerini ayrıca yapın. Kuyruk yönetimi ve session/cache seçimi rehberleriyle ayrıntıları tamamlayın; aldığınız hizmetin yönetim sorumluluklarını ayrıca teyit edin.

Teknik Kaynaklar

Sıkça Sorulan Sorular

Herkes için doğru tek bir havuz sayısı yoktur. Bu rehber, aynı uygulamanın session taşıyan HTTP istekleri ve API istekleri için iki havuz gösterir. Ayrı admin havuzunu ancak farklı bir yük veya erişim gereksinimi haklı çıkarıyorsa ekleyin. CLI kuyruk işçileri FPM havuzu değildir. Bütün havuzları, master süreçlerini ve CLI işçilerini host kapasitesine dahil edin; aynı master içindeki havuzlar OPcache’i paylaşır ve ayrı RAM/CPU rezervasyonu oluşturmaz.

Evet. Doğrudan systemd template servisleri, yeniden başlatma, journal logları ve ortak slice üzerinden toplam kaynak sınırları sağlayabilir. Örnekte bütün instance’lar default kuyruğunu dinler; instance numarası kuyruk adı değildir. Supervisor seçerseniz yalnız bu uygulamayı kapsayan ayrı config/socket/PID kullanın. Aynı işçi grubunu iki yöneticiyle birden çalıştırmayın; timeout, yeniden deneme ve kontrollü durdurmayı gerçek uygulamada test edin.

Bu, hizmetin verdiği yetkilere bağlıdır. Klasik paylaşımlı hostingde FPM, Nginx ve systemd ayarlarına doğrudan erişim bulunmayabilir; sağlayıcının desteklediği seçenekleri doğrulayın. Yönetilebilir VPS veya fiziksel sunucuda yetkili yönetici bu düzeni kurabilir. Ayrı havuz verilmesi tam kiracı izolasyonu kanıtı değildir; kimlikler, dosya ve sır erişimi, OPcache kapsamı ve ortak host kaynakları birlikte değerlendirilir.

İşlerin ölçülmüş tepe belleği, CPU/I/O maliyeti, veritabanı bağlantıları, kuyruk yaşı ve HTTP gecikme hedefleri belirleyicidir. Çekirdek başına evrensel işçi kuralı kullanmayın. Rehberdeki dört işçi ve toplam 2 GiB/150% cgroup bütçesi yalnız örnektir. 2048 MiB / 350 MiB yaklaşık beş verir, fakat yönetici ve diğer giderler hesaba katılmadığından güvenli kapasite sayılmaz. Karışık HTTP ve kuyruk yükünde ölçün; zaman aşımı, idempotency ve başarısız iş takibini ayrıca kurun.
↑