| name | laravel-queues |
| description | Laravel Queues, Jobs, Workers, and Horizon patterns. Job design, queue configuration, worker management. Use when implementing background processing or async tasks. |
Laravel Queues
Background processing done right.
When to Use Queues
| Use Case | Why Queue? |
|---|
| Sending emails | Don't block user request |
| Processing payments | Retry on failure |
| Generating reports | Long-running task |
| Syncing with APIs | External failures |
| Image processing | CPU intensive |
| Webhooks | Async notification |
1. Queue Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ Controller โโโโโบโ dispatch โโโโโบโ Queue โ โ
โ โ (sync) โ โ (async) โ โ (Redis) โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโฌโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโผโโโโโโโ โ
โ โ Response โโโโโโ Result โโโโโโ Worker โ โ
โ โ (instant) โ โ (later) โ โ php artisanโ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ queue:work โ โ
โ โโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
2. Creating Jobs
Basic Job
php artisan make:job ProcessOrder
class ProcessOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Order $order,
) {}
public function handle(PaymentService $payment): void
{
$payment->charge($this->order);
}
}
Dispatching
ProcessOrder::dispatch($order);
ProcessOrder::dispatch($order)->delay(now()->addMinutes(10));
ProcessOrder::dispatch($order)->onQueue('payments');
ProcessOrder::dispatchSync($order);
3. Queue Configuration
config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'default',
'retry_after' => 90,
'block_for' => null,
],
],
'high' => env('REDIS_QUEUE', 'high'),
'default' => env('REDIS_QUEUE', 'default'),
'low' => env('REDIS_QUEUE', 'low'),
Queue Priority
SendWelcomeEmail::dispatch($user)->onQueue('emails');
ProcessRefund::dispatch($order)->onQueue('high');
GenerateReport::dispatch($report)->onQueue('low');
Worker Priority
php artisan queue:work --queue=high,default,low
4. Error Handling
Retry Configuration
class ProcessPayment implements ShouldQueue
{
public int $tries = 5;
public int $maxExceptions = 3;
public int $backoff = 60;
public function backoff(): array
{
return [1, 5, 10];
}
public int $timeout = 120;
}
Handle Failures
class ProcessPayment implements ShouldQueue
{
public function handle(): void
{
}
public function failed(Throwable $exception): void
{
Log::error('Payment failed', [
'order' => $this->order->id,
'error' => $exception->getMessage(),
]);
Notification::send($this->order->user, new PaymentFailed($this->order));
}
}
Retry Specific Exceptions
class ProcessPayment implements ShouldQueue
{
public function retryUntil(): DateTime
{
return now()->addHours(1);
}
public function shouldRetry(Throwable $e): bool
{
return !($e instanceof InvalidOrderException);
}
}
5. Job Batching
Create Batch
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
$batch = Bus::batch([
new ProcessOrder($order1),
new ProcessOrder($order2),
new ProcessOrder($order3),
])->then(function (Batch $batch) {
Log::info('Batch completed', ['id' => $batch->id]);
})->catch(function (Batch $batch, Throwable $e) {
Log::error('Batch failed', ['error' => $e->getMessage()]);
})->finally(function (Batch $batch) {
})->dispatch();
$batchId = $batch->id;
Monitor Batch
$batch = Bus::findBatch($batchId);
$batch->totalJobs;
$batch->pendingJobs;
$batch->failedJobs;
$batch->progress();
$batch->finished();
$batch->cancelled();
Cancel Batch
$batch->cancel();
public function handle(): void
{
if ($this->shouldCancel()) {
$this->batch()->cancel();
return;
}
}
6. Job Chaining
Sequential Execution
Bus::chain([
new ValidateOrder($order),
new ChargePayment($order),
new ShipOrder($order),
new SendConfirmation($order),
])->dispatch();
Chain with Catch
Bus::chain([
new ReserveInventory($order),
new ChargePayment($order),
new CreateShipment($order),
])->catch(function (Throwable $e) {
Log::error('Order chain failed', ['error' => $e->getMessage()]);
})->dispatch();
7. Unique Jobs
Prevent Duplicates
use Illuminate\Contracts\Queue\ShouldBeUnique;
class ProcessPodcast implements ShouldQueue, ShouldBeUnique
{
public function __construct(
public Podcast $podcast,
) {}
public function uniqueId(): string
{
return $this->podcast->id;
}
public int $uniqueFor = 3600;
}
Unique Until Processing
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
}
8. Horizon
Installation
composer require laravel/horizon
php artisan horizon:install
Configuration
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'low'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'tries' => 3,
],
],
],
Commands
php artisan horizon
php artisan horizon:pause
php artisan horizon:continue
php artisan horizon:terminate
php artisan horizon:status
Monitoring Metrics
9. Best Practices
Do's
| Practice | Reason |
|---|
| Keep jobs small | Easier to retry |
| Make jobs idempotent | Safe to re-run |
| Use timeouts | Prevent stuck jobs |
| Log job progress | Debug failures |
| Use job tags | Track in Horizon |
Don'ts
| Anti-Pattern | Problem |
|---|
| Large payloads | Serialization issues |
| DB queries in constructor | Stale data |
| Long transactions in jobs | Lock contention |
| Too many retries | Queue backlog |
Idempotent Job Pattern
class ChargePayment implements ShouldQueue
{
public function handle(): void
{
if ($this->order->isPaid()) {
return;
}
$this->paymentService->charge($this->order);
$this->order->markPaid();
}
}
10. Testing Jobs
Fake Queue
use Illuminate\Support\Facades\Queue;
test('order triggers job', function () {
Queue::fake();
$order = Order::factory()->create();
Queue::assertPushed(ProcessOrder::class, function ($job) use ($order) {
return $job->order->id === $order->id;
});
});
Test Job Logic
test('process order charges payment', function () {
$order = Order::factory()->create();
$job = new ProcessOrder($order);
$job->handle(app(PaymentService::class));
expect($order->fresh()->is_paid)->toBeTrue();
});
Commands Reference
php artisan queue:work
php artisan queue:work --queue=high,default
php artisan queue:work --tries=3 --timeout=60
php artisan queue:monitor redis:default,redis:high
php artisan queue:failed
php artisan queue:retry all
php artisan queue:retry 5
php artisan queue:forget 5
php artisan queue:flush
php artisan horizon
php artisan horizon:status
php artisan horizon:terminate
Remember: Queues are for reliability, not just speed. Design jobs to handle failure gracefully.