SyntaxStudy
Sign Up
Laravel Job Chaining, Batching, and Rate Limiting
Laravel Beginner 1 min read

Job Chaining, Batching, and Rate Limiting

Job chaining runs a sequence of jobs in order, where each job is dispatched only after the previous one completes successfully. Chains are created with Bus::chain([]) and dispatched with ->dispatch(). If any job in the chain fails, subsequent jobs are not executed. Chains can also specify a catch callback for global failure handling. Job batches allow dispatching a collection of jobs and tracking their collective completion. Batches are created with Bus::batch([]) and support then(), catch(), and finally() callbacks. The then callback fires when all jobs in the batch complete successfully. Batch state — total, pending, failed, processed counts — is stored in the job_batches table and accessible via the batch instance. Rate limiting jobs prevents hammering external APIs. The RateLimiter facade registers limiters by key, and jobs use the WithoutOverlapping middleware and Limit objects from Illuminate\Queue\Middleware\RateLimited. Job middleware is an array returned from the middleware() method on the job class. The WithoutOverlapping middleware ensures only one instance of a specific job runs at a time, using atomic locks.
Example
<?php
// Job chaining
use Illuminate\Support\Facades\Bus;

Bus::chain([
    new \App\Jobs\OptimizeImage($upload),
    new \App\Jobs\GenerateThumbnails($upload),
    new \App\Jobs\NotifyUser($upload->user),
])->catch(function (\Throwable $e) {
    \Log::error('Chain failed: ' . $e->getMessage());
})->dispatch();

// Job batching
$batch = Bus::batch([
    new \App\Jobs\ImportRow($rows->slice(0, 100)),
    new \App\Jobs\ImportRow($rows->slice(100, 100)),
    new \App\Jobs\ImportRow($rows->slice(200)),
])->then(function (\Illuminate\Bus\Batch $batch) {
    \Log::info('All import jobs completed.');
})->catch(function (\Illuminate\Bus\Batch $batch, \Throwable $e) {
    \Log::error('Batch failed: ' . $e->getMessage());
})->finally(function (\Illuminate\Bus\Batch $batch) {
    // Runs regardless of success/failure
})->dispatch();

// Rate-limited job middleware
use Illuminate\Queue\Middleware\RateLimited;

class CallExternalApi implements \Illuminate\Contracts\Queue\ShouldQueue
{
    public function middleware(): array
    {
        return [new RateLimited('external-api')];
    }
}

// Register the limiter in AppServiceProvider::boot()
// \RateLimiter::for('external-api', fn($job) => \Illuminate\Cache\RateLimiting\Limit::perMinute(20));