İçeriğe geç
Muhammet Şafak
en
Soran: Barış Cevaplandı:

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

  1. 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.

  2. Ara katmanlar stream’i sessizce iptal eder. PHP’de doğru yazsanız bile FastCGI/Nginx buffering açıksa dosya yeniden belleğe birikir.

  3. En ölçeklenebilir yol, uygulamayı veri yolundan çıkarmaktır. Yetkiyi PHP verir, baytı web sunucusu ya da object storage taşır.

Ne yapmalı

  1. 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. Diskteyse X-Accel-Redirect (Nginx) veya X-Sendfile (Apache) ile dosyayı web sunucusu servis etsin.

  2. PHP’den geçirecekseniz chunk-by-chunk stream edin. Laravel’de Storage::readStream() ile dosyayı açın, bir StreamedResponse (ya da response()->streamDownload) döndürün ve callback içinde döngüde fread edip yazıp flush() çağırın. Content-Length ve Content-Disposition header’larını verin.

  3. Output buffering’i o route için kapatın. PHP’de ob_*, Nginx’te fastcgi_buffering/proxy_buffering kapalı olmalı; X-Accel-Buffering: no header’ı Nginx’e “bunu biriktirme” der.

  4. 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.

Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi