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 gereken onu stream etmek — ama daha iyisi, baytı PHP’den hiç geçirmemek.
Sorun kavramsal: bir HTTP cevabını “önce tamamını hazırla, sonra gönder” diye düşünüyorsun. Büyük dosyada doğru model “oku-bas-oku-bas”; üretirken aktarırsın, bellek düz kalır.
- PHP’den geçireceksen chunk-by-chunk stream et. Laravel’de
Storage::readStream()ile dosyayı aç, birStreamedResponse(ya daresponse()->streamDownload) döndür ve callback içinde bir döngüdefreadedip yazıpflush()çağır. Böylece bellek aynı anda yalnızca tek chunk kadar (birkaç MB) dolu kalır.Content-LengthveContent-Dispositionheader’larını ver ki tarayıcı indirme penceresini ve ilerleme çubuğunu doğru göstersin. - Output buffering’i o route için kapat. Asıl tuzak burada: PHP’de
ob_*, FastCGI/Nginx tarafındafastcgi_buffering/proxy_bufferingaçıksa katman dosyayı yeniden belleğe biriktirip stream’i iptal eder. O endpoint’te buffering’i kapat;X-Accel-Buffering: noheader’ı Nginx’e “bunu biriktirme” der. - Octane’de cevabı bellekte tutma. Kalıcı süreçte (Octane/Swoole) koca bir response gövdesini worker’da biriktirirsen bellek istekler arası temizlenmez ve süreç şişer. Stream’i doğrudan çıkışa akıt, ara değişkende tutma.
- En iyisi: baytı uygulamadan hiç geçirme. Ölçekte doğru cevap PHP’yi veri yolundan çıkarmak. Dosya S3/object storage’daysa kısa ömürlü bir
presigned URLüret, kullanıcı doğrudan oradan indirsin. DiskteyseX-Accel-Redirect(Nginx) veyaX-Sendfile(Apache) ile dosyayı web sunucusu servis etsin; uygulama yalnızca yetkiyi kontrol edip header’ı döner.
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 readStream + StreamedResponse ile chunk’la, output buffering’i kapatmayı unutma. Tek kural: 5 GB’ı asla tek seferde belleğe alma — 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.