sammyjo20/laravel-chunkable-jobs
Laravel Chunkable Jobs lets you split large queued jobs into smaller chunks that run within memory and time limits. Set a chunk size, process records in batches, and let each chunk dispatch separately to keep queue workers fast, responsive, and reliable.
composer require sammyjo20/laravel-chunkable-jobs. As of v1.1.0 the package supports Laravel 9–11 and PHP 8.3, so you can adopt it on a modern stack without pinning older framework versions.src/ChunkableJob.php (the contract) and the README examples first; that is the whole surface area.foreach ($users as $user) { ... }) inside a single queue worker. That is the classic timeout/memory killer. Convert it to a chunkable job, set a chunk size, and each chunk becomes its own queued job that re-dispatches the next one.class SyncContacts implements ShouldQueue, ChunkableJob
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Collection $contacts) {}
public function getChunkSize(): int
{
return 250;
}
public function handle(): void
{
foreach ($this->contacts as $contact) {
// only ever 250 items in $this->contacts
}
}
}
Then dispatch with the full collection and let the package slice it: SyncContacts::dispatch($contacts).
--timeout) and Horizon's timeout.imports on a low-concurrency supervisor) and let the chunking do the load-shedding for you.onChunk and failure hooks — confirm exact names in the README for your version). Use them to write progress to cache or a JobRun model so the UI can show "1,200 / 40,000 processed" rather than a spinner.Bus::batch() for fan-out, not fan-in. If you have 20 independent large imports, batch the imports; use chunkable jobs within each import. Don't nest chunkable jobs inside an already-chunked loop.updateOrCreate, upserts, and "already done" guards over blind inserts. Store a cursor/last_id on a run record if you need resumability.Queue::fake() still works — assert the first job was pushed with the correct slice, then call handle() directly with a small collection to test the per-chunk logic. Assert getChunkSize() explicitly in a unit test so a refactor doesn't silently change throughput.SerializesModels re-fetches models on each chunk; if items may be deleted mid-run, pass IDs or plain DTOs instead of Eloquent models to avoid ModelNotFoundException partway through.finally block so failures are visible.$tries = 3 on a chunkable job means a failing chunk retries 3 times, not the whole run — usually what you want, but it also means a permanently failing chunk stops the chain silently. Always implement failed() and alert/log there.->chunkById()) and dispatch one chunkable job per slice, or the originating web request becomes the new bottleneck.ShouldBeUnique will interfere: consecutive chunks look identical to the unique lock and get dropped. Avoid uniqueness on the chunkable job class; use a run ID in the payload instead.composer.json. If you're on PHP 8.3 + Laravel 11, this is the minimum version you should be on, since v1.0.0 predates that matrix.How can I help you explore Laravel packages today?