Sunucu & Performans

PHP-FPM Neden Diskinizi Doldurur? (Coredump Vaka Analizi)

PHP-FPM Neden Diskinizi Doldurur? (Coredump Vaka Analizi)

Kısa cevap: PHP-FPM worker’ları tekrar tekrar çöktüğünde (segfault), systemd her çökmede otomatik olarak bir coredump dosyası kaydeder. Bu dosyalar /var/lib/systemd/coredump/ altında birikir ve her biri onlarca MB olabileceğinden kısa sürede birkaç GB’a ulaşıp diski şişirir — üstelik hiçbir log dosyası veya yedek büyümediği hâlde. Kalıcı çözüm dört adımdır: coredump’ları temizleyip bir üst sınır koymak, OPcache JIT’i kapatmak (WordPress’te faydası olmayan, kararsızlık riski taşıyan bir özellik), pm.max_children‘ı gerçek trafiğe göre hesaplamak ve pm.max_requests ile worker’ları periyodik tazelemek.

Yönetimini yaptığımız çoklu-domain bir Virtualmin sunucusunda, disk kullanımı birkaç gün içinde 5 GB’dan 12 GB’a çıktı. Ne bir yedek alınmıştı, ne bir log dosyası anormal büyümüştü — en azından ilk bakışta öyle görünüyordu. Bu yazı, sorunu adım adım nasıl teşhis edip kalıcı olarak çözdüğümüzü anlatıyor. Sunucu AlmaLinux + Virtualmin/Webmin, ama mantık PHP-FPM ve systemd kullanan tüm modern dağıtımlarda geçerli.

Belirti: “hiçbir şey büyümedi” ama disk doluyor

Disk kullanımı sıçradığında ilk refleks genelde log dosyalarına ya da yedeklere bakmaktır. Burada da öyle yaptık — ama klasik şüpheliler temizdi. Sistematik gitmek için dizinleri büyükten küçüğe sıraladık:

disk kullanımını büyükten küçüğe kır
# Genel disk durumu
df -h

# Root altındaki dizinleri büyüklüğe göre sırala
du -sh /* 2>/dev/null | sort -rh | head -20

# systemd journal'ın disk kullanımı (sık gözden kaçan bir kalem)
journalctl --disk-usage

Bu vakada journal küçüktü (~64 MB), log dizini de makul boyuttaydı. Şüpheli, kırılımı derinleştirdikçe ortaya çıktı: /var/lib/systemd dizini tek başına birkaç GB tutuyordu — bu, normal bir systemd kurulumunda beklenmeyecek kadar büyük bir rakamdır.

Teşhis: coredump dizini

/var/lib/systemd içini kırdığımızda hemen görünür oldu:

/var/lib/systemd içini kır
du -sh /var/lib/systemd/* 2>/dev/null | sort -rh | head -10

ls -lh /var/lib/systemd/coredump/ | head -20

coredump alt dizini neredeyse dizinin tamamını kaplıyordu. İçeriğine bakınca sebep netleşti: onlarca core.php-fpm.*.zst dosyası, hepsi aynı ~10–15 dakikalık pencerede oluşmuş, her biri ~40 MB civarında. Yani PHP-FPM art arda, defalarca çökmüş ve systemd her çökmede yeni bir coredump kaydetmişti.

coredumpctl boş dönerse “coredump yok” sanmayın

Bu vakada coredumpctl list hiçbir kayıt döndürmedi — journal index’i rotate olup silinmişti. Ama fiziksel dosyalar diskte duruyordu. coredumpctl journal’daki index kayıtlarına dayanır; index gidince dosyaların kendisi kalmaya devam eder. Diskteki gerçek boyutu görmek için her zaman /var/lib/systemd/coredump/ dizinine doğrudan bakın. Bu ayrımı bilmemek, doğru teşhisten tamamen uzaklaştırabilirdi.

Kök sebep: JIT + yetersiz worker kapasitesi + bot trafiği

Coredump’ların tarih/saatini Nginx erişim logundaki aynı dakikalarla karşılaştırdığımızda net bir tablo çıktı: o pencerede siteye gelen trafiğin büyük kısmı arama motoru ve AI botlarıydı (Googlebot, Bingbot, PetalBot, çeşitli SEO crawler’ları, LLM crawler’ları) — gerçek kullanıcı trafiği değil. Bot trafiği kısa sürede çok sayıda farklı, önbelleğe düşmemiş sayfaya istek attığında PHP-FPM havuzu (pm.max_children) doygunluğa ulaşıyor, kuyruk oluşuyor ve bu baskı altında bazı worker’lar segfault ile çöküyordu.

Segfault’un asıl tetikleyicisi ise PHP 8’in JIT (Just-In-Time) derleyicisiydi — özellikle tracing modu. OPcache’in kendisi dolduğunda normalde bir crash değil, sadece “cache dolu, sıfırlanıyor” davranışı üretir. Ama JIT açıkken, yoğun eşzamanlı istek altında bilinen kararsızlık senaryoları vardır. WordPress gibi ağırlıklı olarak I/O bekleyen (veritabanı sorgusu, dosya okuma) bir iş yükünde JIT’in ölçülebilir bir hız kazancı da yoktur — yani risk var, kazanç yok.

Özetle zincir şöyleydi: yoğun bot trafiği → worker kapasitesi doygunlaşıyor → JIT altında segfault → her segfault bir coredump → coredump’lar birikip diski dolduruyor.

Çözüm: dört adımda kalıcı düzeltme

1. Coredump’ları temizle ve bir üst sınır koy

Önce mevcut dosyaları silip alanı geri kazanın, sonra systemd’nin coredump için ayırdığı toplam alana bir tavan koyun — böylece aynı senaryo tekrarlansa bile disk tükenmeden önce systemd eski coredump’ları kendisi eler.

coredump temizliği + kalıcı üst sınır
rm -f /var/lib/systemd/coredump/*.zst

mkdir -p /etc/systemd/coredump.conf.d
cat > /etc/systemd/coredump.conf.d/limit.conf << 'EOF'
[Coredump]
SystemMaxUse=500M
MaxUse=500M
EOF
systemctl daemon-reexec

SystemMaxUse=500M, coredump dizininin toplamda 500 MB’ı geçemeyeceği anlamına gelir; limit dolduğunda systemd en eski dosyaları otomatik siler. Bu bir “kök sebep” çözümü değil, bir güvenlik ağıdır — asıl çözüm aşağıdaki adımlardır.

2. JIT’i kapat

AlmaLinux/RHEL tabanlı sistemlerde (Remi repo dahil) PHP’nin OPcache/JIT ayarları standart olarak /etc/php.d/ dizini altındaki bir .ini dosyasında tutulur. Dosya adı sunucudan sunucuya değişebilir, bu yüzden isme göre değil içeriğe göre arayın:

JIT’i bul, kapat, doğrula
# Ayarı hangi dosya veriyor?
grep -rl "opcache.jit" /etc/php.d/ 2>/dev/null

# Bulunan dosyada YALNIZ ilgili satırı değiştir
sed -i 's/opcache.jits*=s*tracing/opcache.jit=disable/' /etc/php.d/<bulunan_dosya>
systemctl reload php-fpm

# Etkin değeri doğrula
php-fpm -i 2>/dev/null | grep "opcache.jit "

Dosyanın tamamını değil, sadece ilgili satırı değiştirin: aynı dosyada session, upload limiti gibi başka önemli ayarlar da olabilir. Önce cat ile içeriğe göz atmanız önerilir.

JIT tek bir site için kapatılamaz

JIT ayarı PHP_INI_SYSTEM seviyesindedir ve OPcache paylaşımlı belleği tüm PHP-FPM havuzları (yani sunucudaki tüm domainler) arasında tek bir instance olarak çalışır. Bu yüzden JIT’i yalnızca tek bir site için kapatmak mümkün değildir — sunucudaki tüm PHP-FPM havuzlarını aynı anda etkiler. Çoklu-domain bir sunucu yönetiyorsanız bu kararı tüm siteleri düşünerek verin. Aynı paylaşım mantığının OPcache belleğinde nasıl bir arıza ürettiğini paylaşımlı OPcache havuzu 502 vakasında ayrıca ele aldık.

3. pm.max_children‘ı gerçek trafiğe göre hesapla

Worker sayısını rastgele büyütmek, düşük RAM’li sunucularda başka bir soruna (OOM) davetiye çıkarır; çok düşük bırakmak ise yoğun anlarda 502 hatasına yol açar. Doğru yaklaşım bir formülle gitmektir:

Güvenli üst sınır ≈ (PHP-FPM’e ayrılabilecek RAM) ÷ (pool’un memory_limit değeri)

Sunucudaki toplam RAM’den MySQL/MariaDB, Nginx, ClamAV gibi diğer servislerin kullandığı payı çıkarın; kalanı PHP-FPM’e ayrılabilecek bütçe olarak alın. Bu bütçeyi her pool’un php_value[memory_limit] değerine bölerek o pool için makul bir tavan bulursunuz. Örneğin bütçe 1.5 GB, pool’un memory_limit‘i 128 MB ise teorik tavan ~11–12’dir; trafiğe göre biraz pay bırakıp 8–14 arasında bir değer seçmek gerçekçidir.

512 MB gibi yüksek memory_limit‘e sahip havuzlarda (genelde WooCommerce/ağır eklentili siteler) worker başına ayak izi büyük olduğundan sayıyı düşük tutmak, düşük memory_limit‘li havuzlarda ise trafiğe göre biraz daha yüksek gitmek dengeyi korur. Sunucudaki tüm havuzların mevcut değerlerini tek komutla görüp planlayabilirsiniz:

tüm havuzların kapasite tablosu
for f in /etc/php-fpm.d/*.conf; do
  echo "=== $(basename "$f") ==="
  grep -E "^user|^pm.max_children|^pm.max_requests|^php_value[memory_limit]" "$f"
done

4. pm.max_requests ile worker hijyeni

pm.max_children, aynı anda kaç worker’ın çalışabileceğini; pm.max_requests ise tek bir worker’ın kaç istek işledikten sonra yeniden başlatılacağını belirler. Varsayılan 0‘dır, yani worker hiç yeniden başlamaz — bu da zamanla küçük bellek sızıntılarının (kötü yazılmış bir eklenti veya extension kaynaklı) birikip worker’ı şişirmesine izin verir. 500 gibi bir değer, her worker’ı düzenli aralıklarla “temiz sayfa” olarak resetleyip bu birikimi engeller; günlük kullanımda fark edilmeyecek kadar küçük bir maliyeti vardır.

pm.max_requests ekle + config’i test et
# İlgili pool dosyasında pm.max_requests satırı yoksa ekle:
grep -q "pm.max_requests" /etc/php-fpm.d/<pool>.conf || 
  echo "pm.max_requests = 500" >> /etc/php-fpm.d/<pool>.conf

php-fpm -t && systemctl reload php-fpm

php-fpm -t, config’i uygulamadan önce sözdizimi hatası olup olmadığını kontrol eder — hatalı bir satır tüm PHP-FPM servisinin ayağa kalkmamasına yol açabileceğinden bu adımı atlamamak önemlidir.

Webmin/Virtualmin’den GUI ile yapmak isteyenler için

Terminale girmeden yapmak isteyenler için üçüncü ve dördüncü adım Virtualmin panelinden de uygulanabilir:

  • İlgili domain’e girin → Server ConfigurationPHP-FPM Configuration (domain FPM modunda çalışıyorsa bu seçenek görünür). pm.max_children, pm.start_servers, pm.max_requests alanları doğrudan formdan düzenlenir; kaydettiğinizde arka planda tam olarak yukarıdaki .conf dosyasını günceller.
  • Aynı domain’de Server ConfigurationPHP Configuration, custom php.ini direktifi eklemek için kullanılabilir. JIT ayarı burada değil, sunucu genelinde /etc/php.d/ altında olduğu için bu ekrandan sadece o domain’e özel override eklenebilir — JIT gibi paylaşımlı bir ayar için terminal daha güvenilirdir.
  • Yeni açılacak domainlerde bu değerlerin otomatik gelmesini istiyorsanız Server Templates → kullandığınız template → PHP çalışma modu bölümündeki varsayılan pm.max_children/pm.max_requests alanlarını güncelleyin; o andan sonra oluşturulan her domain bu değerlerle açılır.

Panelin geri kalanını ve güvenlik tarafını Virtualmin/Webmin sunucu sıkılaştırma rehberimizde ele aldık.

Sonuç: ölçülmüş fark

Metrik Önce Sonra
Disk kullanımı (/) ~12 GB ~9.4 GB
/var/lib/systemd/coredump ~3.8 GB 0 (üst sınır 500 MB)
JIT tracing (açık) disable
PHP-FPM segfault/coredump Onlarca, ~30 dakikalık pencerede Düzeltme sonrası 0
Worker yenilenmesi (pm.max_requests) Yok (sınırsız ömür) 500 istekte bir
Sayfa yükleme hızı Değişmedi (beklenen sonuç)

Not: pm.max_children ve JIT değişikliklerinin sayfa açılış hızına ölçülebilir bir etkisi olmadı — bu beklenen bir sonuçtu, çünkü bu ayarlar performansı değil, eşzamanlı yük altındaki kapasite ve kararlılığı hedefliyor.

Hangi sunucuda hangi reçete?

  • Gerçek trafiği düşük ama bot/crawler trafiği yoğun siteler: Asıl kaldıraç pm.max_children‘ı büyütmek değil, sayfa/FastCGI önbelleğini sıkılaştırmaktır — botların çoğu isteği PHP’ye hiç düşmeden önbellekten karşılanmalı. Worker sayısını yalnızca eşzamanlı önbellek-miss patlamasını tolere edecek kadar (genelde 8–16) tutmak yeterlidir.
  • Gerçek kullanıcı trafiği yüksek siteler: pm.max_children‘ı formülle (RAM bütçesi ÷ memory_limit) hesaplayıp trafiğe orantılı büyütün; pm.max_requests=500 ile worker hijyenini ihmal etmeyin.
  • RAM’i dar (4 GB ve altı) sunucular: Worker sayısını agresif artırmak yerine JIT’i kapatmak ve önbelleği güçlendirmek daha ucuz ve daha güvenli bir kazançtır — RAM marjı dar sunucularda kapasiteyi büyütmek, ClamAV OOM vakasında olduğu gibi başka bir OOM riskini büyütebilir.
  • Çoklu-domain paylaşımlı sunucular: JIT sunucu genelinde tek bir ayardır, tüm havuzları etkiler; kararı en yoğun/en kritik site üzerinden değil, sunucudaki tüm havuzların toplam RAM baskısı üzerinden verin.

Sıkça sorulan sorular

pm.max_children‘ı neden çok yüksek tutmuyoruz, kapasite fazlası zarar mı verir?

Evet — her worker, pool’un memory_limit değerine kadar bellek kullanabilir. Worker sayısını RAM bütçesinin üzerinde tutmak, tüm worker’lar aynı anda yoğunlaşırsa sunucuyu OOM’a (bellek tükenmesi) sürükleyebilir. Doğru sayı, gerçek eşzamanlı yükü karşılayacak kadar olan, RAM bütçesini aşmayan sayıdır.

JIT’i kapatmak WordPress performansını düşürür mü?

Pratikte hayır. JIT, ağır CPU-bound hesaplama içeren iş yüklerinde (matematiksel işlemler, sıkı döngüler) fayda sağlar. WordPress gibi ağırlıklı olarak veritabanı sorgusu ve dosya G/Ç’si bekleyen bir uygulamada JIT’in ölçülebilir bir hız katkısı görülmez; buna karşın yoğun eşzamanlı yük altında kararsızlık riski taşır.

Coredump’lar neden coredumpctl list ile görünmüyordu ama diskte duruyordu?

coredumpctl, journal’daki index kayıtlarına dayanır; journal rotate olup eski kayıtlar silindiğinde coredump dosyalarının kendisi diskte kalmaya devam eder ama coredumpctl artık onları listeleyemez. Diskteki gerçek boyutu görmek için her zaman /var/lib/systemd/coredump/ dizinine doğrudan bakmak gerekir.

pm.max_requests ayarlamak sitede kesinti yaratır mı?

Hayır. Bir worker limite ulaştığında mevcut isteği bitirir, sonra kapanır; havuzdaki diğer worker’lar kesintisiz hizmet vermeye devam eder, php-fpm master yerine yenisini başlatır. Kullanıcı tarafında fark edilmez.

Aynı segfault döngüsü tekrar başlarsa nasıl anlarım?

journalctl -u php-fpm içinde “signal 11” ya da “exited on signal” gibi ifadeler ve /var/lib/systemd/coredump/ dizininde yeni dosyalar oluşması ilk işaretlerdir. Ayrıca PHP-FPM’in kendi log dosyasında tekrarlayan "reached pm.max_children" uyarıları, kapasitenin gerçekten dolduğunu (segfault olmasa bile) gösterir — bu uyarıların hangi pool’dan geldiğini düzenli takip etmek, sorunu büyümeden yakalamanın en ucuz yoludur.

Coredump’ları tamamen kapatsam olmaz mı?

Kapatmak diski korur ama teşhis yeteneğinizi de alır: bir sonraki çökmede elinizde hiçbir kanıt olmaz. Doğru yaklaşım, SystemMaxUse ile bir tavan koyup coredump’ları sınırlı tutmaktır — hem disk güvende olur hem de son çökmelerin izi elinizde kalır.

DKS pratikte ne yapıyor?

Çoklu-domain paylaşımlı sunucularda her havuzun kaynak ayak izi birbirinden farklıdır ve sunucu genelindeki ayarlar (JIT gibi) tek bir siteye özel düşünülemez. Bir sunucuyu devraldığımızda önce tüm PHP-FPM havuzlarının memory_limit/pm.max_children dağılımını gerçek trafik verisiyle (bot dahil) karşılaştırıyor, JIT gibi sunucu-geneli riskli ayarları standart olarak kapatıyor, coredump ve OOM kayıtlarını düzenli izliyoruz. Bu sürekli izleme ve kapasite planlama işini bakım hizmetimizde üstleniyoruz; sıfırdan doğru kurulum için sunucu kurulumu ve altyapı tarafında çalışıyoruz.

İlgili yazılar

Aynı “az kaynaklı sunucuda servis disiplini” temasında, bellek baskısının farklı bir servisi nasıl çökerttiğini ClamAV OOM vaka analizinde ele aldık. PHP-FPM havuz modu ve OPcache’in genel tuning mantığı için WordPress Sunucu Performansı: PHP-FPM, OPcache, Redis yazımıza, paylaşımlı OPcache havuzunun tek başına 502 üretebildiği vakaya ise OPcache dolunca neden 502 alırsınız yazısına bakabilirsiniz.

Sunucunuzda diskte açıklanamayan bir sıçrama mı var?

Coredump birikimi çoğu zaman gözden kaçan, ama teşhisi ve çözümü net bir sorundur. Bizimle iletişime geçin; disk ve PHP-FPM kayıtlarınızı inceleyip kalıcı bir kapasite planı çıkaralım.

Digital Kernel Software

Digital Kernel Software

WordPress tema geliştirme, hosting ve sunucu performansı üzerine yazıyoruz. İstanbul / Türkiye.

Projeniz mi var?

Okuduğunuzu
projenize uygulayalım

WordPress, hosting ve performans tarafında yazdıklarımızı sizin işinizde hayata geçirelim. Net konuşuruz — pazarlama dili değil, uygulanabilir çözüm.