Skip to content

How to Process Huge CSV Files in Laravel Efficiently with LazyCollection

Process huge CSV files in Laravel without memory issues. Learn how LazyCollection compares to Collection with benchmarks, code examples, and performance tips.

Dasun Kalupahana 2 min read 594 views 0 comments

Processing large CSV files in Laravel can be a memory nightmare. Normal methods load the entire file into memory, causing performance issues. But Laravel’s LazyCollection changes the game by processing data line by line, drastically reducing memory usage.

In this article, we’ll:

✅ Compare Collection vs. LazyCollection
✅ Benchmark performance with real code
✅ Show how LazyCollection saves memory and speeds up processing



💡 Why This Matters?

Handling large CSV files in PHP can be inefficient if done incorrectly. Most developers load the entire file into memory, leading to:

❌ High memory consumption
❌ Slow processing times
❌ Potential server crashes

Laravel’s LazyCollection solves this by lazily loading data, processing one row at a time. Let’s see how it works.



🔍 Code Comparison: Collection vs. LazyCollection

I use the Laravel League package to read the CSV file. You can install it using the command below.

composer require league/csv

1️⃣ Normal CSV Processing (Memory Hog)

Let’s start by looking at the normal approach — using a regular Collection that loads the entire file into memory at once.

<?php

namespace App;

use Illuminate\Support\Collection;
use League\Csv\Reader;

class NormalCsvProcessor
{

    public function processLargeFile($filePath): Collection
    {
        $startMemory = memory_get_usage();

        // Read entire CSV into memory
        $csv = Reader::createFromPath($filePath, 'r');
        $csv->setHeaderOffset(0);

        // Convert to array - loads ALL records into memory
        $records = collect($csv->getRecords());

        echo "Memory used: " . 
                $this->formatBytes(memory_get_usage() - $startMemory) 
             . "\n";

        return $records;
    }

    private function formatBytes($bytes, $precision = 2)
    {
        $units = ['B', 'KB', 'MB', 'GB'];

        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }

        return round($bytes, $precision) . ' ' . $units[$i];
    }
}

❌ Problem:

  • Entire CSV is loaded into memory.
  • Memory usage scales with file size.
  • Slower for large datasets.

2️⃣ LazyCollection (Memory Efficient)

LazyCollection changes everything. Instead of loading data all at once, it streams records one by one:

<?php

namespace App;

use Illuminate\Support\LazyCollection;
use League\Csv\Reader;

class LazyCollectionCsvProcessor 
{
    public function processLargeFile($filePath): LazyCollection
    {
        $startMemory = memory_get_usage();
        
        // Create LazyCollection that reads CSV line by line
        $lazyRecords = LazyCollection::make(function () use ($filePath) {
            $csv = Reader::createFromPath($filePath, 'r');
            $csv->setHeaderOffset(0);
            
            foreach ($csv->getRecords() as $record) {
                yield $record;
            }
        });
        
        echo "LazyCollection created - minimal memory used\n";
        echo "Memory used: " . 
              $this->formatBytes(memory_get_usage() - $startMemory) . "\n";
        
        return $lazyRecords;
    }
    
    private function formatBytes($bytes, $precision = 2)
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        
        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }
        
        return round($bytes, $precision) . ' ' . $units[$i];
    }
}

The Magic of yield

The yield keyword is your secret weapon. It creates a generator that:

  • Produces one record at a time
  • Doesn’t load everything into memory
  • Allows for streaming processing


⏱️ Benchmarking the Difference

I created a Laravel Artisan command to compare performance side by side:

1. Create the command:

php artisan make:command CsvBenchmarkCommand
<?php

namespace App\Console\Commands;

use App\LazyCollectionCsvProcessor;
use App\NormalCsvProcessor;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;

class CsvBenchmarkCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'csv:benchmark';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Benchmark CSV processing performance';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        $filePath = storage_path("app/public/users.csv");

        $this->info("🚀 Starting CSV Processing Benchmark...");
        $this->info("📁 File: {$filePath}");

        $benchmark = Benchmark::measure([
            'NormalCollection' => fn() => 
              (new NormalCsvProcessor)->processLargeFile($filePath),
            'LazyCollection' => fn() => 
              (new LazyCollectionCsvProcessor)->processLargeFile($filePath)
        ], 1);

        $this->displayResults($benchmark);
    }

    private function displayResults($results)
    {
        $this->info("\n📊 Benchmark Results:");
        $this->line("=====================================");

        foreach ($results as $method => $time) {
            $this->line("📈 {$method}: {$time}ms");
        }

        // Calculate improvement
        $normal = $results['NormalCollection'];
        $lazy = $results['LazyCollection'];
        $improvement = round((($normal - $lazy) / $normal) * 100, 2);

        $this->line("\n🎯 Performance Analysis:");
        $this->line("=====================================");
        $this->info("⚡ LazyCollection is {$improvement}% faster!");
        $this->info("🚀 Speed improvement: " . round($normal / $lazy, 2) 
        . "x");
    }
}

2. Run the benchmark using the following command:

I tested it with a 100,000-row CSV file, and here are the results:

php artisan csv:benchmark

3. Performance Improvement

My benchmark proves it:

🔹 Normal Method: 463ms & 51.59MB memory
🔹 LazyCollection: 0.739ms & 856 Bytes memory



🎯 Conclusion: Stop Wasting Resources!

These results speak for themselves:

🔹 626x speed boost
🔹 60,000x less memory
🔹 No more “out of memory” errors

👉 Action Step: Replace your next collect() with LazyCollection and feel the difference!


📢 Your Turn

💬 Have you tried LazyCollection? Share your benchmarks below!
🔥
Follow me for more Laravel performance secrets.

If you found this helpful, feel free to share or drop a comment. Happy coding with Laravel! 🧱✨

Free resources for this topic

Code snippets and fixes for this topic

Get one practical tip every week

New tutorials, fixes and free cheat sheets from DEV Talk, straight to your inbox. No spam, unsubscribe in one click.

Free download: Laravel interview questions (PDF)

Have a question about this post?

Ask the community: other developers and the author can answer.

Ask the community

Get notified about new articles

One click, no email needed. Turn it off anytime with the bell at the top.

Get new articles on your iPhone

Add DEV Talk to your Home Screen, open it, then tap the bell.

Rate this post
0.0
Share

Enjoying the content?

If this article helped you, you can support the author with a coffee. It keeps new posts coming.

Buy me a coffee (opens in a new tab)

Comments 0

Be kind. Comments with links are reviewed before they appear.

No comments yet

Be the first to share your thoughts.

Get new articles in your inbox

Practical tutorials and deep dives, about once a week. No spam, unsubscribe anytime.

Privacy policy

Add DEV Talk to your Home Screen

  1. 1 Tap the Share button in Safari's toolbar.
  2. 2 Scroll down and tap Add to Home Screen.
  3. 3 Tap Add. Open it from the Home Screen to read like an app and to turn on notifications with the bell.

We use cookies to improve your experience and analyze traffic. By continuing, you agree to our Privacy Policy.