---
title: Use chunk()/cursor() for Large Datasets — Never Model::all()
impact: HIGH
impactDescription: Model::all() on a large table loads all rows into memory, causing OOM errors and PHP timeouts
tags: chunk, cursor, memory, performance, eloquent, laravel
---

## Use chunk()/cursor() for Large Datasets

`Model::all()` or `->get()` with no limit fetches every row into PHP memory. On a 100k-row table this causes memory exhaustion.

**Wrong:**

```php
// Memory bomb — loads all rows into PHP array
$users = User::all();
foreach ($users as $user) {
    $user->sendNewsletter();
}

// Slightly better but still: loads 50k orders at once
$orders = Order::where('status', 'pending')->get();
foreach ($orders as $order) {
    ProcessOrder::dispatch($order);
}
```

**Correct:**

```php
// chunk: fetches 200 rows at a time, re-queries per batch
User::chunk(200, function ($users) {
    foreach ($users as $user) {
        $user->sendNewsletter();
    }
});

// cursor: uses lazy collection (PHP generator), one row in memory at a time
// Best for read-only iteration without modifying records
foreach (User::cursor() as $user) {
    $user->sendNewsletter();
}

// chunkById: safer than chunk for updates (avoids row-skip bug)
User::where('status', 'inactive')
    ->chunkById(500, function ($users) {
        User::whereIn('id', $users->pluck('id'))->delete();
    });

// For commands/jobs that process everything
User::select(['id', 'email', 'name']) // only needed columns
    ->where('newsletter', true)
    ->chunkById(1000, function ($batch) {
        SendNewsletterJob::dispatch($batch->pluck('id')->all());
    });
```

**Rule of thumb:** any query without `->limit()` or `->paginate()` in a loop or report command must use `chunk()` or `cursor()`.
