Hafıza Tükenme Tuzağı
<p>B2B SaaS mühendisliğinde, müşteri veri ihracı gibi 500.000 uygulama kaydını, müşteri listesini veya işlem geçmişini CSV formatında indirmenin sağlanması kaçınılmaz bir gerekliliktir. Pek çok backend geliştiricisinin hemen ilk refleksi, veritabanından satırları alıp bu dosya içeriğini tamamen PHP hafızasında oluşturmak ve <code>Storage::download()</code> veya <code>response()->download()</code> kullanmaktır.</p>
<p>Küçük veri kümesi için bu işlevsel olabilir ancak veri kümesi büyüdüğünde uygulamanız çökebilir. Eğer 500.000 Eloquent kaydını hafızaya yükleyerek bir CSV dosyası oluşturursanız, PHP anında <code>memory_limit</code> sınırını aşar ve <code>Allowed memory size exhausted</code> hatası fırlatır. Sunucu isteği düşürür ve kullanıcıya boş bir indirme ve bozuk bir gösterge bırakır. Güvenli bir şekilde ölçeklenmek istiyorsanız, dosyaları kademeli olarak akıtmanız gerekir.</p>
<h2>Çözüm: `response()->streamDownload()`</h2>
<p>Milyonlarca veritabanı satırını tamamen RAM’e yüklemek yerine, dosya verilerini akıtmalıyız. Akıtma işlemi, Laravel uygulamanızın veritabanından tek bir satırı okuyup, bunu hemen istemcinin tarayıcısına göndermesine ve sunucu hafızasından hemen aşağı atmasına olanak tanır. Sunucunuzun bellek kullanımı, 100 satır veya 10 milyon satır çıkarıyor olsanız da birkaç megabaytta sabit kalır.</p>
<h3>Akıtmalı CSV İhracının Mimarisi</h3>
<p>Laravel’ın yerel <code>streamDownload()</code> işlevini, veritabanı <strong>Cursors</strong> ile birleştiriyoruz. Bir sorgu kursörü, kayıtları birer birer almak için düşük seviyeli veritabanı sürücülerini kullanarak, tüm koleksiyonu Eloquent modellerine yüklemekten kaçınır.</p>
<pre><code>namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ExportController extends Controller
{
/
Sunucu belleğini tüketmeden büyük bir CSV ihracı akıtın.
*/
public function exportLogs(Request $request): StreamedResponse
{
$tenantId = $request->user()->tenant_id;// 1. Tarayıcıya hemen akıtılmış bir yanıt döndür
return response()->streamDownload(function () use ($tenantId) {// 2. PHP çıkış tampon akışını aç $file = fopen('php://output', 'w'); // CSV BOM ve Başlık satırını ekleyin fputs($file, chr(0xEF).chr(0xBB).chr(0xBF)); fputcsv($file, ['ID', 'Eylem', 'IP Adresi', 'Zaman Damgası']); // 3. Eloquent kursörünü kullanarak kayıtları güvenli bir şekilde birer birer sorgulayın $logs = AuditLog::where('tenant_id', $tenantId) ->orderBy('created_at', 'desc') ->cursor(); // Bellek tahsisi O(1) kalır foreach ($logs as $log) { // Belirli satır verilerini ağ akış tamponuna yazın fputcsv($file, [ $log->id, $log->action, $log->ip_address, $log->created_at->toIso8601String(), ]); // Her döngü iterasyonundan hemen sonra belleği boşaltın unset($log); } // 4. Açık dosya göstericisini temizleyin fclose($file);}, ‘massive-export-‘ . now()->format(‘Y-m-d’) . ‘.csv’, [
‘Content-Type’ => ‘text/csv’,
‘Cache-Control’ => ‘no-cache, must-revalidate’,
]);
}
}
<h2>Mühendislik ROI'si</h2>
<p>Akıtılmış yanıtların uygulanması, savunmasız bir ihracat sistemi oluşturur. Bir veritabanı kursörünü <code>streamDownload()</code> ile birleştirerek, backend tamamen RAM dalgalanmalarından kaçınır. Platformunuz, diğer gelen isteklere hızlı ve yanıt veren bir API sunarken, yüzlerce eşzamanlı kurumsal ihracı rahatlıkla yönetebilir.</p>Kaynak: Orijinal Makale


