# 2-5 GB dosyaları PHP belleğini şişirmeden stream ile nasıl indirtirim?

> 2-5 GB dosyayı presigned URL ya da X-Accel-Redirect ile PHP'nin dışından servis edin; zorundaysanız readStream ile chunk'layıp buffering'i kapatın.

- Soruldu: 2026-05-22
- Yanıtlandı: 2026-05-25
- Soran: Barış
- Etiketler: performans, laravel, altyapi
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/buyuk-dosyalari-php-bellegini-sismeden-stream-ile-indirmek/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**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?


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.

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.

1. **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 bir döngüde `fread` edip yazıp `flush()` çağırın. Böylece bellek aynı anda yalnızca tek chunk kadar (birkaç MB) dolu kalır. `Content-Length` ve `Content-Disposition` header'larını verin ki tarayıcı indirme penceresini ve ilerleme çubuğunu doğru göstersin.
2. **Output buffering'i o route için kapatın.** Asıl tuzak burada: PHP'de `ob_*`, FastCGI/Nginx tarafında `fastcgi_buffering`/`proxy_buffering` açıksa katman dosyayı yeniden belleğe biriktirip stream'i iptal eder. O endpoint'te buffering'i kapatın; `X-Accel-Buffering: no` header'ı Nginx'e "bunu biriktirme" der.
3. **Octane'de cevabı bellekte tutmayın.** Kalıcı süreçte (Octane/Swoole) koca bir response gövdesini worker'da biriktirirseniz bellek istekler arası temizlenmez ve süreç şişer. Stream'i doğrudan çıkışa akıtın, ara değişkende tutmayın.
4. **En iyisi: baytı uygulamadan hiç geçirmeyin.** Ölçekte doğru cevap PHP'yi veri yolundan çıkarmak. 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; 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ı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.

## İlgili Yazılar

- [İşlediğim satırları aynı anda güncellerken chunk yerine chunkById mı kullanmalıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/isledigim-satirlari-ayni-anda-guncellerken-chunk-yerine-chunkbyid-mi-kullanmaliyim/) — Sor Bakalım
- [Eloquent'te N+1'i erken yakalamak için Model::preventLazyLoading'i yalnızca lokalde mi yoksa production'da da mı açmalıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/eloquentte-n-1i-erken-yakalamak-icin-model-preventlazyloadingi-yalnizca-lokalde-mi/) — Sor Bakalım
- [API sunucumu Cloudflare Tunnel arkasına alıp 443'ü internete kapatmalı mıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/api-sunucusunu-cloudflare-tunnel-arkasina-mi-almali/) — Sor Bakalım
