Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 52 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20263 min read

Handling Large Data Exports: Performance, Queues, and Streaming

Learn to handle large data exports in Laravel without hitting memory limits or timeouts. Discover how to stream CSVs to S3 using queued background jobs.

LaravelPerformanceQueuesCSVArchitecturephpbackend

Previously in this course, we covered Database Connection Pooling to keep our database interactions efficient. This lesson builds on that foundation by addressing the "Export Problem": when a user requests a report containing hundreds of thousands of rows, traditional request-response cycles fail due to PHP memory limits and execution timeouts.

To build a production-grade SaaS, you must treat data exports as asynchronous background tasks.

The Problem with Synchronous Exports

When you attempt to fetch 50,000 Eloquent models and convert them into a CSV in a single request, you hit two walls:

  1. Memory Exhaustion: Eloquent models are heavy objects. Loading them all into an array consumes hundreds of megabytes.
  2. Execution Timeouts: Browsers and load balancers (like Nginx/AWS ALB) will drop the connection if the server takes longer than 30–60 seconds to respond.

We solve this by decoupling the export process into a queued job that streams data directly to a cloud storage bucket (like S3).

Architecting the Async Export Flow

Instead of returning a file directly to the user, the flow becomes:

  1. Request: The user triggers an export; the controller dispatches a GenerateReportJob.
  2. Queue: The worker picks up the job and begins streaming data.
  3. Storage: The worker writes the file to S3 using League\Csv or Laravel's built-in Storage facade.
  4. Notification: Once finished, the job triggers an event to notify the user (via WebSockets or Email) that their download is ready.

Worked Example: Streaming with League\Csv

First, ensure you have league/csv installed. It is the industry standard for memory-efficient CSV generation.

PHP
namespace App\Jobs;

use App\Models\User;
use League\Csv\Writer;
use Illuminate\Support\Facades\Storage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;

class GenerateUserReport implements ShouldQueue
{
    use Queueable;

    public function handle()
    {
        $filePath = 'exports/users-' . now()->timestamp . '.csv';
        $stream = fopen('php:#6A9955">//temp', 'r+');
        $csv = Writer::createFromStream($stream);

        #6A9955">// Add header
        $csv->insertOne(['ID', 'Name', 'Email', 'Created At']);

        #6A9955">// Use chunkById to keep memory usage constant
        User::query()->chunkById(1000, function ($users) use ($csv) {
            foreach ($users as $user) {
                $csv->insertOne([$user->id, $user->name, $user->email, $user->created_at]);
            }
        });

        #6A9955">// Upload to S3
        Storage::disk('s3')->put($filePath, $stream);
        fclose($stream);

        #6A9955">// Dispatch event to notify user...
    }
}

By using chunkById(), we ensure that only 1,000 records reside in memory at any given time, regardless of whether we are exporting 1,000 or 1,000,000 rows.

Why Streaming is Superior

MethodMemory UsageTimeout RiskUX
Collection::all()High (O(n))HighPoor (Loading spinner)
cursor()Low (O(1))ModerateBetter
Queued StreamingMinimal (O(1))NoneExcellent (Async)

Hands-on Exercise

  1. Create a new ExportReport job.
  2. Implement the chunkById pattern to fetch records from your core domain model.
  3. Use a temporary local file or php://temp to construct the CSV.
  4. Upload the final file to your configured Storage disk.
  5. Add a Notification trigger at the end of the handle() method to inform the user via your frontend (e.g., using Laravel Echo).

Common Pitfalls

  • Ignoring Memory Limits: Even with chunkById, if you eager-load relationships inside the loop, you will blow up your memory. Only select the columns you need: User::select(['id', 'name', 'email'])->chunkById(...).
  • Database Timeouts: For extremely large exports, the database connection might time out during the loop. If this happens, ensure your DB_READ_TIMEOUT is configured appropriately or perform periodic reconnect() calls if necessary.
  • Storage Latency: Do not write directly to the local disk of your web server; use php://temp or a dedicated temporary directory that is cleared after the job finishes to avoid filling up server storage.
  • Lack of User Feedback: Never leave a user hanging. Always provide a "Processing..." state in the UI and notify the user once the file is ready for download.

Recap

Performance in data-heavy SaaS applications relies on avoiding "bloated" requests. By moving exports to the background, you keep your web processes lean and your application responsive. Always stream using chunking to maintain a flat memory profile, and leverage your queue system to handle the heavy lifting.

Up next, we will look at Security Header Configuration to ensure our exported files and browser sessions remain locked down against modern web threats.

Previous lessonDatabase Connection PoolingNext lesson Security Header Configuration
Back to Blog

Similar Posts

LaravelJune 28, 20263 min read

Handling Large File Uploads: Streaming to S3 & Async Processing

Master scalable file uploads in Laravel. Learn to stream directly to S3 and process heavy files asynchronously to keep your application fast and memory-efficient.

Read more
LaravelJune 27, 20264 min read

Queue Worker Prioritization: Architecting Laravel Job Hierarchy

Master Laravel queue worker prioritization by implementing named queues. Learn to isolate critical tasks from background jobs for a scalable, responsive system.

Part of the course

Advanced Laravel: Architecture, Scaling & Performance

advanced · Lesson 52 of 57

  1. 1

    Transitioning from MVC to DDD

    3 min
  2. 2

    Defining Bounded Contexts

    3 min
  3. 3

    Implementing Action Classes

    3 min
Read more
LaravelJune 28, 20264 min read

Database Sharding Concepts: Architectural Scaling for Laravel

Sharding is the final frontier for high-concurrency apps. Learn how to plan for data sharding, select partition keys, and manage cross-shard queries in Laravel.

Read more
4

Utilizing Data Transfer Objects (DTOs)

3 min
  • 5

    Service Layer Pattern

    4 min
  • 6

    Modular Monolith Structure

    3 min
  • 7

    Querying with Strict Eloquent

    4 min
  • 8

    Advanced Subqueries and Joins

    4 min
  • 9

    Raw Expressions for Performance

    4 min
  • 10

    Advanced Indexing Strategies

    4 min
  • 11

    Database Partitioning Techniques

    4 min
  • 12

    Read/Write Database Splitting

    4 min
  • 13

    Handling Multi-Database Connections

    3 min
  • 14

    Eloquent Caching Strategies

    3 min
  • 15

    Queue Worker Prioritization

    4 min
  • 16

    Unique Job Patterns

    4 min
  • 17

    Rate Limiting Background Jobs

    3 min
  • 18

    Event-Driven Architecture

    4 min
  • 19

    Integrating External Message Brokers

    4 min
  • 20

    Distributed Transactions and Sagas

    3 min
  • 21

    Eventual Consistency Patterns

    4 min
  • 22

    Multi-Layered Caching Strategy

    4 min
  • 23

    Cache Tagging and Invalidation

    4 min
  • 24

    Session Persistence in Clusters

    4 min
  • 25

    High-Availability Infrastructure

    4 min
  • 26

    Zero-Downtime Deployment Pipelines

    4 min
  • 27

    Advanced OAuth2 Implementation

    3 min
  • 28

    JWT and Stateless Security

    4 min
  • 29

    Multi-Tenant Security Isolation

    3 min
  • 30

    Defense Against SSRF

    3 min
  • 31

    Mass Assignment Hardening

    4 min
  • 32

    Automated Security Testing

    3 min
  • 33

    Custom Telemetry Design

    3 min
  • 34

    Distributed Tracing

    4 min
  • 35

    Profiling PHP Execution

    3 min
  • 36

    Memory Management in Long-Running Processes

    4 min
  • 37

    Testing DDD Components

    3 min
  • 38

    Contract Testing

    3 min
  • 39

    Handling Large File Uploads

    3 min
  • 40

    Optimizing Asset Pipelines

    4 min
  • 41

    Database Query Caching Layers

    3 min
  • 42

    Advanced Eloquent Scopes

    4 min
  • 43

    Distributed Locks

    3 min
  • 44

    API Versioning Strategies

    4 min
  • 45

    Database Migration Strategies

    4 min
  • 46

    Handling Webhooks Securely

    3 min
  • 47

    Advanced Logging Patterns

    3 min
  • 48

    Database Indexing for Joins

    4 min
  • 49

    Graceful Degradation

    3 min
  • 50

    Custom Middleware Development

    4 min
  • 51

    Database Connection Pooling

    4 min
  • 52

    Handling Large Data Exports

    3 min
  • 53

    Security Header Configuration

    3 min
  • 54

    Database Sharding Concepts

    4 min
  • 55

    Real-time Data Synchronization

    3 min
  • 56

    Database Deadlock Prevention

    4 min
  • 57

    Managing Third-Party API Integrations

    3 min
  • View full course