Göğüs hastalıkları ve acil tıp kliniklerinde asistan hekimlere ilk öğretilen hayati uyarılardan biri, ileri evre Kronik Obstrüktif Akciğer Hastalığı (KOAH) olan bir hastaya yaklaşım kuralıdır.
KOAH hastası nefes darlığı içinde, dudakları morarmış halde acil servise gelir. Panik içindeki hasta yakını duvardaki oksijen tüpünü görür ve hekime yalvarır:
"Doktor bey ne olur vanayı sonuna kadar aç! Görmüyor musun hastam nefes alamıyor, sonuna kadar kökle oksijeni!"
Oysa o panikle vanayı sonuna kadar açıp hastaya %100 saf oksijen maskesi bağlarsanız ne olur biliyor musunuz? Hastayı kendi ellerinizle öldürürsünüz!
Çünkü sağlıklı bir insanda beynin nefes alma dürtüsü kandaki karbondioksit artışıyla tetiklenirken; yıllardır yüksek karbondioksitle yaşayan KOAH hastasının beyni bu mekanizmayı kaybetmiş ve nefes almayı sadece "kandaki oksijen düşüklüğüne" (hipoksik dürtü) bağlamıştır. Siz hastaya kontrolsüzce yüksek doz oksijen verdiğiniz anda, beyin "Kanda bol oksijen var, nefes almama gerek kalmadı" der; hastanın solunumu aniden durur, karbondioksit narkozuna girer ve hasta komaya girerek arrest olur.
Sorun ortamdaki oksijenin azlığı değildir; sorun hastanın o oksijeni işleme yeteneğini kaybetmiş olmasıdır. Vanayı açmak şifa değil, ölüm getirir.
Web hosting dünyasında, teknik destek kuyruklarında ve cPanel panellerinde her gün yüzlerce kez yaşanan meşhur bir altyapı psikodraması vardır: Kötü Yazılmış Bir Script İçin Sürekli memory_limit Değerini Artırmaya Çalışan Müşterinin "504 Gateway Timeout" İsyanı.
"Bana 2 Gigabayt RAM Verin, Sitem Uçsun!"
Paylaşımlı hostingde barınan tipik bir yerel müşteri düşünün.
Sitede ağır bir e-ticaret teması, üzerine kurulmuş 35 eklenti ve arka planda tedarikçiden 50 bin ürünü aynı anda çekmeye çalışan verimsiz bir XML ürün içe aktarma (import) botu çalışmaktadır.
Kullanıcı yönetim panelinden "Ürünleri Güncelle" butonuna basar.
Tarayıcının sekmesindeki o minik daire dönmeye başlar: Döner... Döner... Döner...
Bir dakika geçer, iki dakika geçer.
Ve ekrana Nginx veya LiteSpeed web sunucusunun o meşhur gri hatası tokat gibi iner:
HTTP 504 Gateway Timeout
Veya PHP'nin çaresiz çığlığı:
Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 14589234 bytes) in /home/site/public_html/import.php on line 84
Müşteri hemen cPanel'deki "Select PHP Version" veya MultiPHP INI Editor menüsüne koşar.
Orada sihirli bir ayar görür: memory_limit = 256M.
Müşteri sevinçle o sayıyı değiştirir: 512M yapar!
Sayfayı yeniler: Yine aynı hata!
Müşteri bu kez 1024M (1 GB) yazmaya çalışır; fakat hosting firmasının CloudLinux güvenlik kuralları gereği tavan sınır 512 MB'a kilitlenmiştir.
Ve destek sistemine o büyük harfli, öfkeli bilet (ticket) düşer:
"Kardeşim ben sunucunuza dünyanın parasını ödüyorum! Siteme 1 GB RAM yetmiyor, memory limitimi acilen 2048M yapın! Ayarı kitlemişsiniz, parasını verdiğim kaynağı benden esirgiyorsunuz! Hemen limiti kaldırın yoksa hostingi iptal ederim!"
Bellek Sızıntısı (Memory Leak): Dipsiz Kuyuya Su Taşımak
Sistem yöneticisinin anlamakta en çok zorlandığı şey müşterinin bu akıl almaz talebi değil; müşterinin yazılım mantığına olan mutlak yabancılığıdır.
Bir PHP scripti neden 512 Megabayt belleği tüketip çöker?
Normal şartlar altında, optimize edilmiş kurumsal bir PHP uygulaması (hatta karmaşık bir Laravel veya Symfony API'si bile) tek bir sayfa isteği sırasında en fazla 15 ila 35 Megabayt RAM tüketir!
Eğer bir PHP sayfası tek bir ziyaretçiye yanıt verebilmek için 512 MB belleği bitiriyorsa; orada bir "ihtiyaç" yoktur.
Orada açık ve net bir "Bellek Sızıntısı" (Memory Leak) ve Kötü Kodlama Rezilliği vardır!
Arka planda ne olmaktadır?
- Yazılımcı veritabanından 50.000 satırlık ürün tablosunu tek bir
SELECT *sorgusuyla belleğe çekmiştir! - Her bir satırı bir PHP objesine dönüştürüp devasa bir diziye (array) atmıştır.
- Bir döngü (
foreach) açmış, döngünün içinde işi biten değişkenleri bellekten temizlememiş (unset), PHP'nin çöp toplayıcısına (Garbage Collector) fırsat tanımamıştır. - Bellek şişmiş, şişmiş ve sonunda sunucunun izin verdiği tavana toslayarak patlamıştır!
Şimdi o müşteriye istediği o 2 GB RAM limitini verseniz ne olur?
Hiçbir şey değişmez!
O doyumsuz ve bozuk kod, 50 bin ürünü değil 100 bin ürünü çekmeye çalışacak; bu kez 10 saniye sonra 2 GB belleği de yutacak ve yine Allowed memory size exhausted diyerek yere çakılacaktır!
Dipsiz bir kuyuya bir kova su dökseniz de dolmaz, bir tanker su dökseniz de dolmaz. Sorun suyun miktarında değil; kuyunun altındaki deliktedir!
"504 Gateway Timeout" ve İşlemci Boğulması
Bellek yetmeyince müşteri bu kez işlemcinin çalışma süresini zorlamaya başlar:
max_execution_time = 300 (5 dakika!).
Ön tarafta Nginx veya Apache gibi bir ters vekil sunucu (Reverse Proxy) beklemektedir. Nginx, arkadaki PHP-FPM işlemcisine isteği iletir: "Al bu sayfayı çalıştır ve bana HTML çıktısını ver."
Nginx'in bir sabır sınırı vardır: Genellikle 60 saniye.
PHP o kötü yazılmış kodun içinde veritabanında döngülere girip takla atarken, 60 saniye boyunca tek bir bayt bile çıktı üretemez.
Ve Nginx arkadaki PHP'nin donup kaldığına karar verir:
İletişimi keser ve kullanıcıya o soğuk mesajı yapıştırır:
HTTP 504 Gateway Timeout.
Yani Nginx der ki: "Arkadaki kapı bana 60 saniyedir cevap vermiyor, öldü herhalde."
Müşteri ise yine desteğe bağırır: "Sunucunuz zaman aşımına uğruyor, timeout süremi 10 dakikaya çıkarın!"
Bir web sayfasının açılması için bir insanın 10 dakika ekrana bakmasını bekleyen bir zihniyet!
Paylaşımlı Sunucuda "Adalet" Duvarı
Eğer bir hosting firması o müşterinin her istediği memory_limit = 2048M ve max_execution_time = 600 talebine "evet" derse ne olur?
O sunucuda barınan diğer 300 masum müşterinin hayatı kararır!
O tek müşterinin bozuk scripti sunucunun 64 GB'lık RAM'ini saniyeler içinde sömürür, Linux çekirdeğinin hafıza koruma katili olan OOM Killer (Out of Memory Killer) devreye girer ve sunucudaki MySQL veritabanı servisini pat diye öldürür!
Tüm sunucu çöker.
İşte bu yüzden CloudLinux ve LVE limitleri, hosting dünyasının emniyet kemeridir. O limitler müşteriyi kısıtlamak için değil; müşteriyi kendi cehaletinden ve komşularının sorumsuzluğundan korumak için vardır.
Çözüm Vanayı Açmakta Değil, Kodu Tedavi Etmektedir
Bir sistem yöneticisinin görevi müşterinin her istediği zararlı ilacı ona yazmak değildir:
- Chunking (Parçalayarak İşleme): Veritabanından 50 bin satır tek seferde çekilmez! Veri 500'erli paketler halinde (
LIMIT 500 OFFSET 0) belleğe alınır, işlenir, bellek boşaltılır ve sonraki pakete geçilir. PHP belleği asla 50 MB'ı geçmez. - Arka Plan İş Kuyrukları (Cron / Queue Worker): Uzun süren import veya e-posta gönderim işlemleri asla bir web tarayıcısı isteği (HTTP request) içinde çalıştırılmaz! Bu tür ağır işler arka planda CLI üzerinden çalışan bağımsız kuyruk yöneticilerine (RabbitMQ, Redis Queue veya basit bir cron scriptine) devredilir.
- Bellek Profilleme (Xdebug / Blackfire): Hangi fonksiyonun kaç megabayt bellek yuttuğunu analiz eden profilleme araçlarıyla kodun içindeki o kara delikler tek tek kapatılmalıdır.
Tıpta nefes darlığı çeken KOAH hastasına oksijen vanasını sonuna kadar açan hekim hastayı öldürür.
Hosting firması da bellek sızdıran script'e sınırsız RAM veren bir acemi olamaz.
Çözüm belleğin vanasını açmakta değil; kodu ameliyat masasına yatırıp o dipsiz bellek deliklerini dikmektedir. Kodu iyileştirin; çünkü hiçbir sunucu, sonsuz bir döngünün açlığını doyuramaz.