Laravel Queues: Background Jobs Done Right
Laravel Queues: Tips & Tricks
Laravel Queues: Tips & Tricks
Tip: Use ShouldQueue on Notifications
class InvoicePaid extends Notification implements ShouldQueue
{
// Automatically queued, no job class needed
}
Gotcha: Closure Jobs Can't Be Serialized
You can't queue a job with a closure. Use a class-based job instead.
Tip: Chain Jobs for Sequential Processing
Bus::chain([
new ProcessPodcast($podcast),
new OptimizePodcast($podcast),
new PublishPodcast($podcast),
])->dispatch();
If any job fails, the rest won't run.
Tip: Use --tries and --timeout Together
php artisan queue:work --tries=3 --timeout=60
Without --timeout, a stuck job blocks the worker forever. Without --tries, a failed job is lost.
Gotcha: Database Queue Needs Cleanup
The jobs table grows forever. Schedule cleanup:
$schedule->command('queue:prune-batches')->daily();
Tip: Redis Queue is Faster Than Database
For production, always prefer Redis over the database driver. Database queues cause table locking under load.
Tip: Monitor Queue with Horizon
composer require laravel/horizon
php artisan horizon:install
Horizon gives you a dashboard for queue metrics, failed jobs, and throughput.
Gotcha: Model Serialization
When you pass a model to a queued job, Laravel serializes only the identifier. The model is re-fetched when the job runs. If the model was deleted, the job will fail.
Tip: Use route:cache Carefully
php artisan route:cache is fast, but it doesn't work with closure-based routes. Every time you cache routes, Laravel serializes them. If you have Route::redirect() or closure callbacks, the cache breaks. Stick to controller-based routes in production.
Tip: Model APP_KEY Rotation
Rotating APP_KEY invalidates all encrypted data — cookies, encrypted DB columns, and password reset tokens. If you must rotate (e.g., after a leak), plan a migration that re-encrypts existing data with the new key.
Gotcha: Local Scope Leaks
Global scopes defined in booted() apply to ALL queries on that model — including relationships. An innocent User::all() in admin panel might exclude soft-deleted users if a global scope is active.
Senior Insight
Queues are where Laravel apps either shine or silently fail. The most common mistake I see is not handling failed jobs properly — teams set --tries=3 without a corresponding failed_jobs table or notification. I've also seen database queues bring down production because the jobs table grew to millions of rows without pruning. Set up Horizon or at minimum queue:prune-batches on a schedule before you go live, not after the first outage.
Source: Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/laravel), Spatie Blog (https://spatie.be/blog)