RAM yetersizliği, sunucu performans sorunları arasında en ani ve en sert olanıdır. CPU kotasına çarptığında sistem yavaşlar, disk doyduğunda ağırlaşır — ama bellek bittiğinde davranış doğrusal olmaktan çıkar ve bir uçuruma dönüşür. Bir an her şey normaldir, sonraki an sunucu yanıt vermemeye başlar, süreçler öldürülür ve site 500 hatası döner. Aradaki geçiş genellikle dakikalar sürer.
Bu keskinliğin sebebi, belleğin tükendiğinde sistemin başvurduğu iki mekanizmadır: takas alanı (swap) ve OOM killer. Birincisi belleği diske taşıyarak sistemi ayakta tutmaya çalışır ve bunu yaparken performansı kat kat kötüleştirir; ikincisi ise bellek tamamen bittiğinde bir süreci seçip öldürür. Bu yazıda ikisinin de nasıl çalıştığını, bellek baskısını nasıl ölçeceğini, swap'ın ne zaman dost ne zaman düşman olduğunu ve PHP-FPM havuzunu doğru boyutlandırmayı anlatacağım.
Linux Belleği Nasıl Kullanır ve free Çıktısı Neden Yanıltır#
Bellek teşhisinde ilk düzeltilmesi gereken şey free komutunun okunma biçimidir. Çoğu kişi çıktıdaki free sütununa bakar, düşük bir sayı görür ve paniğe kapılır. Oysa Linux boş belleği kasten kullanır: disk önbelleği (page cache) olarak doldurur, çünkü boş duran bellek israftır. Bir uygulama bellek istediğinde çekirdek bu önbelleği anında bırakır.
Bakman gereken sütun available'dır; bu, "uygulamalar takasa düşmeden hemen ne kadar bellek alabilir" sorusunun cevabıdır:
# Bellek durumu - "available" sütununa bak, "free" sütununa DEĞİL
free -m
# total used free shared buff/cache available
# Mem: 7982 3120 284 210 4578 4310
# Swap: 2047 96 1951
# Yukarıdaki örnekte free sadece 284 MB ama available 4310 MB -> sistem rahat
Bu örnekte free yalnızca 284 MB görünüyor ama available 4310 MB; sistem gayet rahat. Panik gerektiren tablo şudur: available toplam belleğin yüzde 10'unun altına inmiş ve Swap satırındaki used değeri artıyor.
| Gösterge | Sağlıklı | Dikkat | Kritik |
|---|---|---|---|
available / toplam | %25 üstü | %10-25 | %10 altı |
| Swap kullanımı | Sabit ve düşük | Yavaşça artıyor | Hızla artıyor |
vmstat si/so | 0'a yakın | Ara sıra sıfırdan büyük | Sürekli sıfırdan büyük |
| OOM kayıtları | Yok | Ayda bir | Günlük |
Bu tablodaki en değerli satır üçüncüsüdür. Swap'ın kullanılıyor olması tek başına sorun değildir — Linux nadiren erişilen bellek sayfalarını arka planda takasa taşır ve bu sağlıklı davranıştır. Sorun, aktif takas trafiğidir: yani sayfaların sürekli diske yazılıp geri okunması. vmstat çıktısındaki si (swap in) ve so (swap out) sütunları bunu gösterir ve ikisi de sürekli sıfırdan büyükse sistem "thrashing" durumundadır.
Swap Kullanımı Hızı Neden Bu Kadar Bozar#
Aradaki fark, büyüklük mertebesi düzeyindedir. Bellek erişimi nanosaniyelerle, NVMe disk erişimi mikrosaniyelerle ölçülür — yani takasa düşen bir bellek sayfası, erişim süresini yaklaşık bin kat uzatır. SATA disk ya da ağ üzerinden bağlı bir depolamada bu fark daha da açılır.
Asıl yıkıcı olan tek bir yavaş erişim değil, döngüdür. Sistem bellek sıkıştığında en az kullanılan sayfaları diske taşır; ama o sayfalara yine ihtiyaç duyulur, geri okunur, bu sefer başka sayfalar taşınır. Süreçler birbirinin sayfalarını sürekli diske itmeye başlar. Bu noktada işlemci neredeyse boştur (vmstat çıktısında wa tavan yapar, us düşer) ama sistem yanıt vermez hale gelir. Bu manzarayı gördüğünde CPU eklemek hiçbir işe yaramaz.
Aktif takas trafiğini gerçek zamanlı izlemek için:
# si ve so sütunları sürekli sıfırdan büyükse thrashing var
vmstat 2 10
# procs -----------memory---------- ---swap-- -----io---- --system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 1 3 98420 12044 2100 340012 480 620 1240 1980 3200 5100 12 8 22 58 0
# Yukarıda: si=480 so=620 sürekli -> takas trafiği var, wa=58 -> sistem diski bekliyor
# Hangi süreç en çok takas kullanıyor
for p in /proc/[0-9]*; do
s=$(awk '/VmSwap/{print $2}' "$p/status" 2>/dev/null)
[ -n "$s" ] && [ "$s" -gt 0 ] && echo "$s KB $(cat $p/comm 2>/dev/null)"
done | sort -rn | head -10
İkinci komut, hangi sürecin belleğinin takasa taşındığını doğrudan listeler. Sonuç genellikle öğreticidir: takasa düşen şey çoğu zaman aktif web süreçleri değil, uzun süredir çalışan ve nadiren dokunulan yardımcı servislerdir. Bu durumda takas kullanımı zararsızdır. Ama listenin başında PHP-FPM işçileri ya da veritabanı süreci varsa, aktif iş yükü diske itiliyor demektir ve bu doğrudan sayfa sürelerine yansır.
OOM Killer: Bellek Tamamen Bittiğinde#
Takas alanı da dolduğunda ya da hiç yoksa, çekirdek son çare olarak OOM killer'ı (out of memory killer) devreye sokar. Bu mekanizma, sistemi tamamen kilitlenmekten kurtarmak için bir süreci seçip öldürür. Seçim, sürecin bellek kullanımına ve önem puanına göre yapılır ve genellikle en çok bellek kullanan süreç seçilir — ki bu çoğu zaman veritabanı sunucusudur.
Bu, karakteristik bir arıza tablosu üretir: site aniden veritabanı bağlantı hatası verir, veritabanı servisi durmuştur ve hiçbir uygulama günlüğünde bunun sebebi yazmaz. Cevap sistem günlüğündedir:
# OOM olaylarını sistem günlüğünde ara
journalctl -k | grep -i -E "out of memory|oom-kill|killed process" | tail -20
# Dağıtıma göre alternatif konumlar
grep -i -E "out of memory|oom-kill" /var/log/syslog /var/log/messages 2>/dev/null | tail -20
# Örnek satır:
# kernel: Out of memory: Killed process 1842 (mysqld) total-vm:4821000kB, anon-rss:2140000kB
Böyle bir satır bulduysan teşhis kesindir: sunucun bellek yetersizliğinden bir servisi kaybetmiş. Bu durumda yapılacak ilk iş servisi yeniden başlatmak değil, neden o kadar belleğin tükendiğini bulmaktır; aksi halde birkaç saat sonra aynı şey tekrar olur.
Kritik servisler için koruma da kurabilirsin. systemd altında çalışan bir servisin OOM tarafından seçilme olasılığını düşürmek mümkündür, ama bunu dikkatli kullan: veritabanını korursan çekirdek başka bir şeyi öldürür, sorunu ortadan kaldırmaz sadece taşır. Kalıcı çözüm her zaman bellek kullanımını gerçekten düşürmek ya da bellek eklemektir.
Belleği Kim Yiyor: Süreç Düzeyinde Teşhis#
Bellek tüketicisini bulmanın anahtarı, doğru sütuna bakmaktır. VSZ (sanal boyut) neredeyse hiçbir şey ifade etmez — sürecin ayırdığı ama kullanmadığı adres alanını içerir. Bakman gereken RSS'tir (resident set size), yani gerçekten fiziksel bellekte tutulan miktar:
# Gerçek bellek kullanımına göre sıralı ilk 15 süreç (RSS, KB cinsinden)
ps -eo pid,user,rss,vsz,etime,comm --sort=-rss | head -16
# PHP-FPM işçilerinin ortalama ve toplam gerçek bellek kullanımı
ps -ylC php-fpm --no-headers 2>/dev/null | \
awk '{sum+=$8; n++} END {if(n) printf "İşçi: %d Ortalama: %.1f MB Toplam: %.1f MB\n", n, sum/n/1024, sum/1024}'
# MySQL/MariaDB tarafında tampon havuzu ayarı
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';" 2>/dev/null
İkinci komutun çıktısı, havuz boyutlandırmanın temelidir. Tipik bir WordPress kurulumunda işçi başına 50-80 MB, ağır eklentili bir mağazada 120-200 MB görmek normaldir. Bu sayıyı bilmeden pm.max_children değerini ayarlamak kumar oynamaktır.
Bir de bellek sızıntısı meselesi var. Uzun süre çalışan bir PHP işçisi, kötü yazılmış bir kod yüzünden istek başına bir miktar belleği bırakmayabilir; yüzlerce istek sonra o işçi yüzlerce megabayt tutuyor olur. ps çıktısında bazı işçilerin diğerlerinden kat kat büyük RSS değerine sahip olması bunun işaretidir.
PHP-FPM Havuzunu Doğru Boyutlandırmak#
Bellek tükenmesinin sunuculardaki bir numaralı sebebi, PHP-FPM havuzunun elde olan bellekten fazlasına izin verecek şekilde ayarlanmasıdır. Hesap aslında basittir ve iki adımdan oluşur.
Önce uygulamalara ayrılabilecek belleği bul: toplam bellekten işletim sistemi, veritabanı ve web sunucusu paylarını çıkar. Sonra bunu bir işçinin ortalama RSS değerine böl.
# 8 GB sunucu örneği için kaba hesap
# Toplam: 8192 MB
# İşletim sistemi: -800 MB
# MySQL (buffer pool): -2048 MB
# Nginx + diğer: -400 MB
# --------------------------------
# PHP'ye kalan: 4944 MB
# İşçi başına ortalama: 80 MB
# max_children = 4944 / 80 = ~61 (güvenlik payıyla 50 yaz)
Bu hesabı yaptıktan sonra yapılandırma şöyle görünür:
; www.conf - 8 GB sunucu, işçi başına ~80 MB varsayımı
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 6
pm.max_spare_servers = 18
; Sızıntı yapan kodun etkisini sınırla: işçi 500 istekten sonra yenilenir
pm.max_requests = 500
; Takılan istek işçiyi süresiz meşgul etmesin
request_terminate_timeout = 60s
pm.max_requests ayarı özellikle önemlidir ve çoğu kurulumda ayarsız bırakılır. Bu değer sayesinde her işçi belirli sayıda istekten sonra yeniden başlar ve biriktirdiği belleği bırakır; yani bellek sızıntısı olan bir eklentinin etkisini kalıcı olmaktan çıkarır. Sıfır bırakırsan işçiler sonsuza kadar yaşar ve zamanla bellek tüketimi tırmanır.
Yaygın hata: pm.max_children değerini "daha çok kapasite" düşüncesiyle büyük vermek. Bu sezgi yanlıştır. Bellekten fazlasına izin verirsen tepe yükte sistem takasa düşer ve performans yarıya değil, onda bire iner; üstelik bu noktada CPU da boşta beklediği için hiçbir kaynağını kullanamazsın. Doğru davranış, kuyruğa girmeyi kabul etmektir: 50 işçi hızlı çalışıp kuyruğu eritmek, 120 işçinin takasta sürünmesinden her zaman iyidir. CPU tarafındaki karşılığını CPU limiti aşımı kaynaklı yavaşlama yazısında ele aldım.
Swap Ayarları: Ne Kadar ve Nasıl#
Swap'ı tamamen kapatmak yaygın bir tavsiyedir ve genellikle yanlıştır. Swap'sız bir sistemde bellek sıkıştığında ara kademe yoktur; doğrudan OOM killer devreye girer ve veritabanın ölür. Küçük bir takas alanı, sistemin nadiren kullanılan sayfaları boşaltarak nefes almasını sağlar ve seni sert çöküşten korur.
Doğru yaklaşım, swap'ı var ama isteksiz hale getirmektir. Bunu swappiness parametresi kontrol eder: değer ne kadar düşükse çekirdek takasa başvurmakta o kadar isteksiz olur.
# Mevcut değeri gör (varsayılan genelde 60)
cat /proc/sys/vm/swappiness
# Sunucu iş yükü için makul bir değer: 10
sudo sysctl -w vm.swappiness=10
# Kalıcı hale getir
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.d/99-sunucu.conf
# Disk önbelleği baskısını da hafiflet (isteğe bağlı)
echo "vm.vfs_cache_pressure=50" | sudo tee -a /etc/sysctl.d/99-sunucu.conf
sudo sysctl --system
Masaüstü dağıtımlarının varsayılan swappiness=60 değeri, etkileşimli kullanım için ayarlanmıştır ve sunucuda gereğinden agresiftir. 10 civarı bir değer, "gerçekten sıkışmadıkça takasa dokunma" anlamına gelir.
Boyut konusunda pratik kural: 8 GB üstü sunucularda toplam belleğin çeyreği ile yarısı arası, ama en fazla birkaç gigabayt yeterlidir. Çok büyük bir takas alanı, sistemin thrashing durumunda çok daha uzun süre sürünmesine izin verir — hızlı bir OOM bazen saatlerce süren bir felçten iyidir.
Bir uyarı: bazı sanal sunucu paketlerinde takas alanı hipervizör tarafında ağ üzerinden bağlı bir depolamada durur ve orada takas kullanımı normalden de yıkıcıdır. Kendi VDS'inde takas dosyasının hangi disk üzerinde olduğunu kontrol et; disk gecikmesinin genel etkisi için disk I/O darboğazı tespiti yazısına bakabilirsin.
Bellek İhtiyacını Gerçekten Azaltmak#
Bellek eklemek her zaman bir seçenektir ama önce tüketimi düşürmek daha ucuzdur. Dört etkili adım var.
Birincisi, önbellek katmanı. Tam sayfa önbelleği devredeyken istek PHP'yi hiç başlatmaz; yani o istek için işçi belleği hiç ayrılmaz. Anonim ziyaretçi oranı yüksek bir sitede bu, bellek ihtiyacını dramatik biçimde düşürür. Kurulum için Nginx FastCGI cache yapılandırma ya da LiteSpeed Cache yazılarına bakabilirsin.
İkincisi, veritabanı tampon havuzunu doğru ayarlamak. Bu değer hem çok küçük hem çok büyük olabilir. Çok küçükse veritabanı sürekli diske gider; çok büyükse diğer servislere yer kalmaz ve sistem takasa düşer. Adanmış bir veritabanı sunucusunda toplam belleğin yaklaşık yüzde 60-70'i yaygın bir başlangıçtır, ama aynı makinede web sunucusu ve PHP de çalışıyorsa bu oranı belirgin biçimde düşürmen gerekir.
-- Havuz isabet oranını kontrol et
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- Innodb_buffer_pool_reads (diskten) / read_requests (toplam) oranı
-- binde birkaçın üstündeyse havuz küçük demektir
Üçüncüsü, PHP bellek sınırını gerçekçi tutmak. memory_limit değerini 512 MB gibi yüksek bir sayıya çekmek, tek bir kaçak isteğin yarım gigabayt tüketmesine izin vermek demektir. 128-256 MB çoğu uygulama için yeterlidir; gerçekten daha fazlasına ihtiyaç duyan tekil işler (içe aktarma, yedekleme) için o betiğe özel yükseltme yapabilirsin.
Dördüncüsü, gereksiz servisleri kapatmak. Kullanılmayan bir veritabanı motoru, ikinci bir arama servisi ya da kurulup unutulmuş bir izleme aracı sessizce yüzlerce megabayt tutar. ps -eo rss,comm --sort=-rss | head -20 çıktısını okuyup tanımadığın her satırı sorgula.
Sık Yapılan Hatalar#
free sütununa bakıp panik yapmak. Linux boş belleği disk önbelleği olarak kullanır; bu iyi bir şeydir. Doğru sütun available'dır.
Swap'ı tamamen kapatmak. Ara kademeyi kaldırdığında sistem doğrudan OOM killer'a düşer ve genellikle veritabanını kaybedersin. Swap'ı küçük tut, swappiness değerini düşür ama tamamen silme.
pm.max_children değerini bellekten bağımsız büyütmek. Bu, tepe yükte sistemi takasa iten en yaygın tek hatadır. Hesabı belleğe göre yap ve güvenlik payı bırak.
pm.max_requests ayarını sıfır bırakmak. Sızıntı yapan bir eklentinin etkisi böylece kalıcı hale gelir ve bellek zamanla tırmanır.
500 hatasının sebebini uygulama günlüğünde aramak. OOM killer bir süreci öldürdüğünde uygulama günlüğünde yalnızca sonucu görürsün; sebebi çekirdek günlüğündedir. journalctl -k | grep -i oom ilk bakacağın yer olmalı.
Bellek sorununu CPU sorunuyla karıştırmak. Thrashing sırasında wa tavan yapar ve us düşer; işlemci boştur. Bu tabloda çekirdek eklemek hiçbir şeyi değiştirmez.
Sıkça Sorulan Sorular#
Swap kullanımı görüyorum, sorun mu#
Tek başına sorun değil. Linux nadiren erişilen bellek sayfalarını arka planda takasa taşır ve bu sağlıklı bir davranıştır; birkaç yüz megabaytlık sabit bir takas kullanımı normaldir. Asıl bakman gereken şey aktif takas trafiğidir: vmstat 2 10 çıktısında si ve so sütunları sürekli sıfırdan büyükse sayfalar sürekli diske yazılıp geri okunuyor demektir ve performans ciddi biçimde bozulur.
Sunucumun RAM'i yetiyor mu nasıl anlarım#
Üç göstergeye bak. Birincisi free -m çıktısındaki available değerinin toplam belleğe oranı; yüzde 10'un altındaysa sıkıntı var. İkincisi aktif takas trafiği (vmstat çıktısındaki si/so). Üçüncüsü çekirdek günlüğündeki OOM kayıtları. Bu üçünü tepe saatte ölç; gece yarısı alınan bir ölçüm hiçbir şey söylemez.
OOM killer neden hep veritabanını öldürüyor#
Çünkü seçim büyük ölçüde bellek kullanımına göre yapılır ve tipik bir sunucuda en çok bellek tutan süreç veritabanıdır. Bu, veritabanının suçlu olduğu anlamına gelmez; genellikle asıl sebep PHP havuzunun bellekten fazlasına izin verecek şekilde ayarlanmış olmasıdır. Veritabanını OOM'dan korumak sorunu çözmez, sadece kurbanı değiştirir.
Swappiness değerini kaça ayarlamalıyım#
Sunucu iş yükleri için 10 civarı yaygın ve makul bir değerdir; "gerçekten sıkışmadıkça takasa dokunma" anlamına gelir. Masaüstü dağıtımlarının varsayılan 60 değeri etkileşimli kullanım için ayarlanmıştır ve sunucuda gereğinden agresiftir. Sıfıra çekmek ise takası fiilen kapatmaya yakın davranır ve sert OOM riskini artırır, bu yüzden önerilmez.
PHP-FPM max_children değerini nasıl hesaplarım#
Önce uygulamalara ayrılabilecek belleği bul: toplam bellekten işletim sistemi, veritabanı ve web sunucusu paylarını çıkar. Sonra bunu bir işçinin ortalama gerçek bellek (RSS) kullanımına böl ve sonucun biraz altında bir değer yaz. Ortalama RSS'i ps -ylC php-fpm çıktısından ölçebilirsin; tahmin etme, ölç. Kuyruğa girmeyi kabul etmek, takasa düşmekten her zaman iyidir.
RAM eklemek mi yoksa optimizasyon mu daha mantıklı#
Önce optimizasyon, çünkü çok daha ucuzdur ve kalıcıdır. Tam sayfa önbelleği kurmak, veritabanı tampon havuzunu doğru ayarlamak, memory_limit değerini gerçekçi tutmak ve kullanılmayan servisleri kapatmak çoğu sunucuda bellek ihtiyacını belirgin biçimde düşürür. Bunları yaptıktan sonra hâlâ tepe saatte available değerin dibe vuruyorsa, ihtiyacın gerçekten daha fazla bellektir.
Kapanış#
Bellek yönetiminde akılda tutman gereken dört alışkanlık var. Birincisi, free sütununu değil available sütununu izle ve ölçümlerini tepe saatte al. İkincisi, swap'ı kapatma ama swappiness değerini düşürerek isteksiz hale getir; ara kademeyi kaldırmak seni doğrudan OOM'a iter. Üçüncüsü, PHP-FPM havuzunu tahminle değil ölçülmüş RSS değeriyle boyutlandır ve pm.max_requests ayarını mutlaka ver. Dördüncüsü, bir 500 hatasında önce çekirdek günlüğüne bak — OOM kaydı varsa aradığın cevap oradadır.
Ölçümlerin gerçekten daha fazla belleğe ihtiyacın olduğunu gösteriyorsa Clou.TR tarafında büyümek kolay. Kaynakların tamamen sana ayrıldığı VDS sunucu paketlerinde belleği ihtiyacına göre seçebilir, değişken yüklerde esneyen bulut sunucu ya da sürekli yüksek yük için dedicated sunucu çözümlerini değerlendirebilirsin. Paylaşımlı bir pakette bellek sınırlarına takılıyorsan NVMe diskli web hosting paketlerine bakabilir, PHP-FPM ve veritabanı ayarlarını kendin kurcalamak istemiyorsan sunucu yönetimi hizmetimize bırakabilirsin.