2-5 GB dosyaları PHP belleğini şişirmeden stream ile nasıl indirtirim?
Soru
Kullanıcılar 2-5 GB'lık log arşivlerini ZIP olarak indiriyor. PHP'de dosyayı `file_get_contents()` ya da `Storage::get()` ile okuyup döndürmeye çalışınca `memory_limit` aşılıyor ve istek patlıyor. Belleği birkaç megabaytta sabitleyip dosyayı diskten chunk-by-chunk okuyup doğrudan HTTP response stream'ine basan yapıyı nasıl kurarım?
Cevap
Kısa cevap: file_get_contents/Storage::get dosyanın TAMAMINI belleğe alır; 2-5 GB için yapmanız gereken onu stream etmek — ama daha iyisi, baytı PHP’den hiç geçirmemek.
Kısa cevap
Sorun kavramsal: bir HTTP cevabını “önce tamamını hazırla, sonra gönder” diye düşünüyorsunuz. Büyük dosyada doğru model “oku-bas-oku-bas”; üretirken aktarırsınız, bellek düz kalır. Dosyayı uygulamanın diskinden çıkarma kararının kendisini yerel diskten S3’e geçiş kaydında ayrıca ele almıştım.
Neden
-
Bellek limiti bir tavan değil, tasarımın sonucudur.
memory_limit’i yükseltmek 5 GB’lık bir dosyayı 5 GB’lık bir RAM sorununa çevirir; model değişmedikçe sınır hep geri gelir. -
Ara katmanlar stream’i sessizce iptal eder. PHP’de doğru yazsanız bile FastCGI/Nginx buffering açıksa dosya yeniden belleğe birikir.
-
En ölçeklenebilir yol, uygulamayı veri yolundan çıkarmaktır. Yetkiyi PHP verir, baytı web sunucusu ya da object storage taşır.
Ne yapmalı
-
En iyisi: baytı uygulamadan hiç geçirmeyin. Dosya S3/object storage’daysa kısa ömürlü bir
presigned URLüretin, kullanıcı doğrudan oradan indirsin. DiskteyseX-Accel-Redirect(Nginx) veyaX-Sendfile(Apache) ile dosyayı web sunucusu servis etsin. -
PHP’den geçirecekseniz chunk-by-chunk stream edin. Laravel’de
Storage::readStream()ile dosyayı açın, birStreamedResponse(ya daresponse()->streamDownload) döndürün ve callback içinde döngüdefreadedip yazıpflush()çağırın.Content-LengthveContent-Dispositionheader’larını verin. -
Output buffering’i o route için kapatın. PHP’de
ob_*, Nginx’tefastcgi_buffering/proxy_bufferingkapalı olmalı;X-Accel-Buffering: noheader’ı Nginx’e “bunu biriktirme” der. -
Octane’de cevabı bellekte tutmayın. Kalıcı süreçte koca bir response gövdesini worker’da biriktirirseniz bellek istekler arası temizlenmez. Stream’i doğrudan çıkışa akıtın, ara değişkende tutmayın.
Sonuç: Ben olsam ilk tercih presigned URL ya da X-Sendfile/X-Accel-Redirect olurdu — PHP yetkilendirir, baytı taşımaz; en ucuz, en ölçeklenebilir yol. Dosyayı gerçekten PHP üzerinden vermek zorundaysanız readStream + StreamedResponse ile chunk’layın, output buffering’i kapatmayı unutmayın. Tek kural: 5 GB’ı asla tek seferde belleğe almayın — ne PHP’de, ne de aradaki katmanda.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.