VPS üzerinde iki, üç, beş PHP uygulaması çalıştırmak — küçük SaaS dünyasında çok normal. Default kurulumda hepsi tek bir www.conf pool’unu paylaşıyor. Bu çoğu zaman çalışıyor — çalışmayana kadar.

Tek pool’un sorunu

Tek pool şu anlama geliyor:

  1. Bir uygulama pm.max_children = 50’yi tüketirse diğerleri response veremez.
  2. Bir uygulamadaki memory leak diğerlerinin worker’larını da OOM’a sürükler.
  3. Bir uygulamayı yeniden başlatmak (örn. opcache reset) hepsinin worker’larını restart eder.

Pool ayrımı bu üç sorunu birden çözüyor.

Pool başına config

/etc/php/8.3/fpm/pool.d/billing.conf:

[billing]
user = billing
group = billing
listen = /run/php/billing.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500

request_terminate_timeout = 30s
catch_workers_output = yes
decorate_workers_output = no
clear_env = no

php_admin_value[memory_limit] = 256M
php_admin_value[error_log] = /var/log/php-fpm/billing-error.log

/etc/php/8.3/fpm/pool.d/notify.conf:

[notify]
user = notify
group = notify
listen = /run/php/notify.sock
; ...
pm.max_children = 8       ; daha hafif uygulama
php_admin_value[memory_limit] = 128M

İki şey kazandık:

  • Filesystem izolasyonuuser = billing ile billing uygulaması notify’ın dosyalarına yazamaz.
  • Resource izolasyonu — billing’in maxchildren’i tükenirse notify hâlâ çalışır.

Nginx tarafında

Her vhost kendi socket’ine bağlanıyor:

server {
    server_name billing.example.com;
    root /var/www/billing/public;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/billing.sock;
        # standart fastcgi params
    }
}

server {
    server_name notify.example.com;
    root /var/www/notify/public;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/notify.sock;
    }
}

opcache shared mı, ayrı mı?

PHP-FPM master process’i opcache’i paylaşıyor — bu yüzden pool’lar arasında opcache otomatik izole değildir. Ancak opcache.validate_root = 1 ve farklı root path’leri kullanılırsa çakışma olmaz. Pratikte: aynı master, ayrı pool’lar yeterli.

İzleme

/status endpoint’ini pool’a açın:

pm.status_path = /status

Sonra Nginx tarafında bunu sadece localhost’a açın:

location ~ ^/status$ {
    access_log off;
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/run/php/billing.sock;
    # ...
}

curl localhost/status ile aktif worker, kuyruk uzunluğu, slow request sayısı gibi metrikleri alıp Prometheus exporter veya basit bir cron’la izleyebilirsiniz.

Ne zaman tek pool yeterli?

Aynı kod tabanının iki kopyasını çalıştırıyorsanız (örn. app.example.com ve staging.example.com) ayrı pool gereksiz. Farklı domain’ler ama aynı süreç sınıfı. Ayrım, uygulamaları operasyonel olarak bağımsız kılmak istediğinizde değerli.