Skip to content
Home PHP Laravel Jobs — Stop Silent Retry Storms

Laravel Jobs — Stop Silent Retry Storms

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Laravel → Topic 10 of 15
Unlimited retries and no failed_jobs caused a payment queue to loop at 100% CPU, processing zero payments.
🔥 Advanced — solid PHP foundation required
In this tutorial, you'll learn
Unlimited retries and no failed_jobs caused a payment queue to loop at 100% CPU, processing zero payments.
  • Queues separate heavy work from HTTP requests, keeping responses fast.
  • Worker loop: pop, unserialize, handle, delete or retry — with memory and timeout guards.
  • Always pass model IDs, not full models, to avoid stale data bugs.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • Queue system offloads slow work from HTTP requests to background workers
  • Each job is a serialized object pushed to a driver (Redis, database, SQS)
  • Worker loop: pop job, unserialize, fire handle(), delete on success or retry on failure
  • maxAttempts and retry_after config prevent infinite retry loops
  • Job batching tracks completion of multiple jobs before running a callback
  • Biggest mistake: relying on model state that changes between dispatch and execution
🚨 START HERE

Laravel Queue: Quick Debug Cheat Sheet

Copy-paste commands for the top three queue emergencies
🟡

Worker not running or stuck

Immediate ActionCheck process existence and logs
Commands
ps aux | grep 'queue:work'
supervisorctl tail laravel-worker stderr
Fix NowRestart supervisor: supervisorctl restart laravel-worker:*
🟡

Job failing silently

Immediate ActionCheck failed jobs table
Commands
php artisan queue:failed
php artisan queue:retry all
Fix NowDisable the failing job queue temporarily by renaming its queue config or setting maxTries=0 on the worker
🟡

Queue jobs piling up, performance degrading

Immediate ActionScale workers
Commands
php artisan queue:work --queue=high,default --sleep=1 --tries=3
supervisorctl status | grep workers
Fix NowAdd more worker processes in supervisor config and reread
Production Incident

The Silent Retry Storm That Took Down a Payment Queue

A misconfigured retry limit on a payment job caused infinite retries, flooding the worker with failing jobs and starving legitimate work.
SymptomPayment processing jobs kept failing with the same error. Workers were at 100% CPU but no payments were actually processed. Logs showed the same job ID retrying every few seconds.
AssumptionThe team assumed the third-party payment gateway was flaky and that retries would eventually succeed.
Root causeThe job's handle() method threw an exception because the order model had been deleted by a separate cleanup process. The job had $tries = null (unlimited retries) and no failed_jobs table was configured, so it retried forever.
FixSet a max $tries on the job class, configure failed_jobs table, and add a conditional check at the start of handle() to bail early if the model no longer exists.
Key Lesson
Always set a finite retry limit on every job.Guard all job handle() methods against stale or deleted models.Monitor the queue length and failed job count in production.
Production Debug Guide

Symptom → Action guide for common queue failures

Jobs are dispatched but never processedCheck if worker is running: ps aux | grep 'queue:work'. Verify queue connection config and that the driver (Redis, DB) is reachable.
Jobs run but always fail with the same errorCheck the failed_jobs table. Run php artisan queue:failed to list failed jobs. Examine the exception stack trace in the payload.
Worker memory grows until OOM killSet --memory limit on worker. Check for unbounded job data (e.g., serialized large collections). Use --tries to limit retry loops.
Jobs processed out of orderCheck queue config: queue names must match. For strict ordering use a single queue worker with --queue=high,default or use the --delay option.

Every production Laravel app hits the same wall eventually: a controller action that sends a welcome email, resizes an uploaded avatar, and pings a third-party webhook starts taking 800ms or more. Users feel it, bounce rates climb, and your infrastructure bill quietly grows because slow requests hold PHP-FPM workers hostage. The fix isn't a faster server — it's getting that work out of the request lifecycle entirely.

Laravel's Queue system is the engine that makes that possible. It lets you serialise a 'job' — a self-contained unit of work — push it onto a queue driver (Redis, SQS, database, etc.), and have a long-running worker process pick it up milliseconds later, completely outside any HTTP request. The framework handles serialisation, retry logic, failure tracking, delayed dispatch, job chaining, and concurrent batches out of the box. But like any powerful engine, it has sharp edges that bite teams who don't understand what's happening under the hood.

By the end of this article you'll understand how the queue worker event loop actually works, why model serialisation silently causes stale-data bugs, how to tune concurrency without melting your database, how to use job batching for fan-out workflows, and what to monitor in production so failures surface before your users notice them.

What is Laravel Queues and Jobs?

Laravel Queues and Jobs is the framework's mechanism for deferring time-consuming tasks out of the HTTP request lifecycle. Instead of processing an email send or image resize inline, you package the work into a Job class and dispatch it. The queue worker — a long-running CLI process — pops jobs from a storage backend (Redis, database, Amazon SQS, or Beanstalkd), unserializes them, and executes the handle() method. This keeps your API responses snappy and allows horizontal scaling of background work.

A typical job class looks like this: it defines a handle() method that receives any dependencies via dependency injection. The constructor stores the data needed to run the job later (e.g., the model ID, not the entire model). You dispatch with dispatch() or dispatchIf() and can specify a queue name, delay, or whether it runs on a specific connection. The framework serializes the job instance to JSON using its __sleep/__wakeup or the SerializesModels trait — and it's that serialization that causes the most common production bugs.

app/Jobs/ProcessPodcast.php · PHP
123456789101112131415161718192021222324252627282930
<?php

namespace App\Jobs;

use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $tries = 5;                     // max attempts
    public $backoff = [10, 30, 60, 120];  // exponential backoff
    public $deleteWhenMissingModels = true;

    public function __construct(
        public int $podcastId,
    ) {}

    public function handle(AudioProcessor $processor): void
    {
        $podcast = Podcast::findOrFail($this->podcastId);
        $processor->transcode($podcast);
    }
}
▶ Output
Job class dispatches a podcast transcoding task to the queue.
Mental Model
Mental Model: Queue as a Ticket System
A queue is just a better-organized todo list that multiple workers can read from concurrently.
  • The dispatcher posts a ticket (job) to a board (queue).
  • Workers grab tickets as fast as they can process them.
  • If a ticket fails, it gets moved to a failed pile — not thrown away.
  • You can put tickets in priority lanes (queue names) to control order.
📊 Production Insight
Serializing the full Eloquent model with SerializesModels reloads the model from the database on handle(). If the model was deleted between dispatch and execution, the job fails with a ModelNotFoundException.
Always pass the model ID (or a value object) instead of the entire model, unless you're certain the model won't change.
Set $deleteWhenMissingModels = true on jobs using SerializesModels to auto-delete the job when the model is gone.
🎯 Key Takeaway
A job is a command object that runs outside HTTP.
Always pass IDs, not full models, to handle().
Use SerializesModels sparingly — know when it reloads.
Choosing a Queue Driver
IfSingle server, simple setup
UseUse database driver — no extra infrastructure needed.
IfMultiple servers, high throughput
UseUse Redis driver — faster than database and supports blocking pop.
IfServerless or AWS-native stack
UseUse Amazon SQS — managed, scalable, integrates with Lambda.
IfNeed job scheduling/timing/visibility timeout controls
UseRedis or SQS. Database driver lacks advanced features like delayed jobs with per-second precision.

How the Queue Worker Loop Actually Works

You run php artisan queue:work and suddenly jobs start getting processed. What's happening inside? The worker is an event loop that:

  1. Pops a job from the queue driver (Redis: BLPOP, database: SELECT FOR UPDATE, SQS: ReceiveMessage).
  2. Unserializes the JSON payload back into a job instance.
  3. Fires the Queue.before event.
  4. Calls the job's handle() method via the container.
  5. If handle() throws an exception: checks retry count. If within $tries, releases the job back to the queue with a delay ($backoff or retry_after). If exceeded, moves the job to failed_jobs.
  6. Fires the Queue.after event.
  7. Loops to step 1.

Each iteration is called a 'job processing cycle'. The worker sleeps for the --sleep duration when the queue is empty. The --timeout option sets the maximum seconds allowed for one job to run. If a job exceeds the timeout, the worker kills the process and moves on — the job will be retried later (because it was never deleted).

Crucially, the worker does NOT restart between jobs. That means any memory leaked during a job's handle() accumulates. The --memory option forces a restart when the worker's memory exceeds that limit (in MB). For long-running workers, always set --memory=128 or similar.

Console/Kernel.php (schedule example) · PHP
1234567891011
// Example: supervisor config to run workers with memory guard
// /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --memory=128
numprocs=4
autostart=true
autorestart=true
user=forge
stdout_logfile=/var/log/worker.log
stderr_logfile=/var/log/worker-error.log
▶ Output
Supervisor configuration for 4 workers on Redis, memory limit 128MB.
⚠ Watch Out: Worker Memory Growth
If you don't set --memory, your worker's memory will grow until the OS OOM-kills it. Always set --memory to at most 80% of your container/VM RAM. For PHP 8.2+ with JIT, start with 128MB and tune from there.
📊 Production Insight
A single job that loads a 10,000-row collection and stores it in a property will balloon memory until the worker restarts.
Use $job->delete() inside handle() if you manually release jobs — forgetting to delete leads to infinite loops.
The --queue flag determines priority: --queue=high,default processes 'high' queue jobs first, then 'default'. Workers starve if a high-priority queue never empties.
🎯 Key Takeaway
Worker = event loop: pop, run, delete or retry.
Set --memory and --timeout on every worker.
Memory grows per job; restart workers periodically.
Worker Concurrency Settings
IfJobs are I/O-bound (HTTP calls, file uploads)
UseRun many workers: 2x CPU cores, each with --timeout=120
IfJobs are CPU-bound (image processing, video transcoding)
UseRun fewer workers: 1 per CPU core, each with --timeout=300
IfJobs touch a shared database
UseLimit workers to avoid connection pool exhaustion. Set --tries low to release failed jobs quickly.

Job Batching: Fan-Out and Aggregation Patterns

Laravel's job batch system lets you dispatch a group of jobs and execute a callback when all of them finish. This is ideal for workflows like: generate 100 report PDFs, then upload a combined archive. Or: process 10,000 records in chunks, then send a notification.

To use batching, define a batchable job that implements ShouldBeUnique — but that's not required. The key is to dispatch the batch via Bus::batch([...])->then(function(Batch $batch) { ... })->catch(...)->finally(...)->dispatch(). The batch ID is stored in the job_batches table. Each job in the batch can read $this->batch() to get the batch instance, and optionally cancel the whole batch by calling $this->batch()->cancel().

Important: batch progress is stored in the database, not in memory. That means if you restart the worker, the batch state persists. However, if the batch table is truncated mid-execution, the batch is lost and callbacks never run.

app/Jobs/ProcessChunk.php · PHP
12345678910111213141516171819202122232425262728293031323334353637383940414243
<?php

namespace App\Jobs;

use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class ProcessChunk implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public array $recordIds) {}

    public function handle(): void
    {
        // If batch was cancelled, bail early
        if ($this->batch() && $this->batch()->cancelled()) {
            return;
        }

        // Process each record...
        foreach ($this->recordIds as $id) {
            // expensive operation
        }

        // Track progress on batch
        $this->batch()->incrementProgress(count($this->recordIds));
    }
}

// In controller:
use Bus;
$batch = Bus::batch([
    new ProcessChunk(range(1, 1000)),
    new ProcessChunk(range(1001, 2000)),
])->then(function () {
    // All chunks processed
})->catch(function () {
    // At least one chunk failed
})->finally(function () {
    // Always runs
})->dispatch();
▶ Output
Batch dispatches two jobs; then callback runs after both complete.
🔥Batch Progress Tracking
Use $batch->progressPercentage() inside your callback to show progress to users via polling or WebSockets. The batch object has totalJobs, pendingJobs, failedJobs properties.
📊 Production Insight
If a job in a batch fails and you have no catch handler, the batch's catch will never fire — the failure just moves the job to failed_jobs.
Always add a catch callback for production batches. Without it, you won't know when a batch partially fails.
Batches with thousands of jobs can cause memory pressure if all job payloads are queued at once. Use chunking.
🎯 Key Takeaway
Batching = dispatch N jobs, run callback when all done.
Always add cancelled() check inside batchable jobs.
Monitor batch completion: add catch and finally callbacks.

Redis vs Database Queue Driver: Real-World Trade-offs

Laravel offers multiple queue drivers. The two most common for self-hosted apps are database and redis. Each has distinct operational characteristics.

Database driver: Uses a jobs table with SELECT ... FOR UPDATE for atomic pop. Simple to set up — no extra service required. But performance degrades under load because each pop acquires a database lock. For high-throughput apps (thousands of jobs per second), the database quickly becomes the bottleneck. Also, delayed jobs require WHERE available_at <= NOW() queries, which can be slow without proper indexing.

Redis driver: Uses Redis lists and blocking pop (BLPOP) for near-instant job retrieval. Supports priority queues natively via ZADD with score. Delayed jobs are stored in sorted sets and only become available after the score time passes. Redis handles high throughput easily, and with Redis Sentinel or Cluster you get HA. The downside: Redis memory is limited. If jobs pile up and memory fills, Redis starts evicting keys. Configure maxmemory-policy noeviction or use a separate Redis instance for queues.

Hybrid approach: Use Redis for your fast queue and database for your reliable queue. The database driver can fall back if Redis goes down.

config/queue.php · PHP
123456789101112131415161718
'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'queue',           // separate Redis connection
        'queue' => 'default',
        'retry_after' => 90,
        'block_for' => null,
        'after_commit' => true,
    ],

    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90,
        'after_commit' => true,
    ],
];
▶ Output
Configuration for both Redis and database connections.
⚠ Redis Memory Limits
If you use Redis for queues, never let it be the same Redis instance that caches user sessions or hot data. A queue backlog can fill Redis and start evicting session keys. Use a dedicated Redis database number (config 'database' => 1) or a separate Redis server.
📊 Production Insight
The database driver's retry_after must be long enough that no job exceeds it. If retry_after is 60s and a job takes 90s, another worker will pick up the same job — creating duplicate processing.
Redis driver's block_for controls whether pop blocks until a job arrives. Set to 5 seconds to avoid busy-waiting.
For extremely high throughput (>1000 jobs/sec), consider Amazon SQS or Beanstalkd — they handle scale better than self-managed Redis.
🎯 Key Takeaway
Database driver is simple but slow under load; Redis is fast but has memory constraints.
Use dedicated Redis instance for queues to avoid eviction.
Set retry_after at least 10% higher than your longest job runtime.

Production Gotchas: Stale Models, Exceptions, and Monitoring

The most common production queue failures all stem from the same root: the world changes between when you dispatch a job and when a worker processes it.

Stale Models: If you pass a model instance to a job, SerializesModels will reload it from the database on handle(). But if another process deleted that model, you get a ModelNotFoundException. Alternatively, if the model's attributes changed (e.g., user email updated), the job uses stale data. Fix: always pass the model ID and query fresh in handle(). Or use $deleteWhenMissingModels = true to auto-cancel stale jobs.

Unhandled Exceptions: If your job throws an exception you don't catch, the worker will retry according to $tries. Eventually the job goes to failed_jobs. But if you don't have a failed job handler (queue:failed-table migration not run), the job is just lost. Always run php artisan queue:failed-table and set up a listener for Queue::failing() to alert your team.

Worker Crashes: If the worker process is killed (OOM, deploy, supervisor restart) while running a job, that job is considered released — it will be retried after retry_after seconds. If you have multiple workers, another worker may pick it up before the original worker finishes, causing duplicate processing. Mitigate by using a job lock or idempotency key in the job's data.

Queue Length Bloat: Monitor queue size with a tool like Horizon or a custom metric (Laravel Pulse, Prometheus). If a queue grows faster than workers can drain it, you need more workers or a faster driver.

app/Providers/AppServiceProvider.php · PHP
123456789101112131415
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\QueueManager;
use Illuminate\Support\Facades\Log;

// In boot() method:
Queue::failing(function (JobFailed $event) {
    Log::critical('Job failed', [
        'job' => $event->job->resolveName(),
        'exception' => $event->exception->getMessage(),
        'payload' => $event->job->getRawBody(),
    ]);

    // Send alert to Slack/PagerDuty
    // e.g., Slack::send("Job {$event->job->resolveName()} failed: {$event->exception->getMessage()}");
});
▶ Output
Event listener for JobFailed to log and alert on every job failure.
💡Idempotency for Job Safety
Design every job so that running it twice has the same effect as running it once. Use database unique constraints or check processing status before performing the action. This prevents double charges, duplicate emails, or double processing.
📊 Production Insight
Running php artisan queue:restart gracefully tells all workers to restart after finishing their current job. But if a job is in an infinite loop (e.g., failed retries that never exhaust), the restart never completes.
Use queue:work --stop-when-empty for one-off processing (e.g., after deploy) to guarantee no jobs linger.
Always test your failover: kill a worker process mid-job and verify the job is retried exactly once.
🎯 Key Takeaway
Stale data is the #1 queue bug — pass IDs not models.
Always run the failed tables migration and set up failure alerts.
Make every job idempotent to handle duplicate execution safely.
🗂 Queue Driver Comparison
Database vs Redis vs SQS
FeatureDatabaseRedisAmazon SQS
Setup effortMinimal (migration)Requires Redis serverRequires AWS account
Pop latency~10ms (lock + query)~1ms (in-memory)~50ms (HTTP API)
Throughput~500 jobs/sec>10000 jobs/sec~10000 jobs/sec
Delayed jobsSupported (query based)Native (sorted set)Supported (visibility timeout)
Job priorityNot built-inVia sorted setsNot built-in
Atomic popSELECT FOR UPDATEBLPOPReceiveMessage (at least once)
PersistenceIn DB — durableIn memory — risk of loss on restartIn S3 — durable
CostOnly database resourcesRedis instance costPer request + storage

🎯 Key Takeaways

  • Queues separate heavy work from HTTP requests, keeping responses fast.
  • Worker loop: pop, unserialize, handle, delete or retry — with memory and timeout guards.
  • Always pass model IDs, not full models, to avoid stale data bugs.
  • Set $tries, $backoff, and configure failed_jobs table on every project.
  • Redis is faster than database but has memory limits; monitor queue depth and failed jobs.
  • Design every job to be idempotent so duplicate execution is safe.

⚠ Common Mistakes to Avoid

    Passing full Eloquent models to jobs
    Symptom

    Jobs fail with ModelNotFoundException when the model was deleted before execution. Jobs may also use stale data if models are updated between dispatch and handle.

    Fix

    Pass model IDs only. In the job handle(), query the model fresh using the ID. If the model must be passed, use the SerializesModels trait with deleteWhenMissingModels=true.

    Forgetting to set a retry limit ($tries) on jobs
    Symptom

    A persistent failure causes the job to retry infinitely, filling logs, consuming worker resources, and potentially hitting rate limits on external APIs.

    Fix

    Set $tries to a small number (3 or 5) on every job class. Use $backoff to space out retries. Configure the failed_jobs table and listen to the JobFailed event for alerting.

    Using the database queue driver without proper indexing
    Symptom

    Queue table queries become slow under load, causing worker lock contention and delayed job pickup. Jobs pile up.

    Fix

    Add indexes on the jobs table for (queue, available_at, reserved_at). Consider migrating to Redis for high-throughput workloads.

    Not monitoring queue length or failed job count
    Symptom

    A silent job failure goes unnoticed until users report missing features (e.g., no welcome email sent). Workers may be stuck processing a problem job while other queues grow.

    Fix

    Set up monitoring with Laravel Horizon, Pulse, or Prometheus exporter for queue size, failed count, and worker process health. Alert on anomalous queue depth.

Interview Questions on This Topic

  • QHow does Laravel's queue system ensure a job is only processed once, even if the worker crashes mid-execution?SeniorReveal
    The queue worker does not delete a job from the queue until after the job's handle() method returns without throwing an exception. If the worker crashes (or the job times out), the job remains in the queue (its 'reserved' flag is reset after retry_after seconds). A different worker will pick it up later. This gives at-least-once delivery. To truly guarantee once-and-only-once, you need idempotency in the job itself — e.g., checking a processing status in the database before performing the action.
  • QWhat is the difference between php artisan queue:listen and php artisan queue:work? When would you use each?Mid-levelReveal
    queue:work starts a persistent worker that stays alive and processes jobs in a loop. It's the production choice — you control it with Supervisor. queue:listen re-boots the framework on every job — it calls php artisan queue:work in a subprocess for each job. listen is useful for development because it reloads code changes, but it's slow for production because starting the framework each job costs ~50-100ms. In production, always use queue:work.
  • QExplain how you would implement a priority queue with Laravel Queues using the Redis driver.SeniorReveal
    Redis queues in Laravel are implemented using sorted sets. The 'queue' name maps to a key like queues:high and queues:default. When you dispatch a job to a named queue (dispatch()->onQueue('high')), it writes to that sorted set. Workers can prioritize queue order with --queue=high,default — they will process all jobs from 'high' before moving on to 'default'. Internally, the worker issues ZRANGEBYSCORE to fetch jobs with score <= current time, then BLPOP on a temporary list for atomic pop. Multiple named queues let you separate critical tasks from background cleanup.
  • QWhat happens inside a worker when a job throws an exception? Walk through the retry flow.Mid-levelReveal
    1. The worker catches the exception. 2. It checks if the job's $tries property is set and if the number of attempts (tracked in the job payload as 'attempts') is less than $tries. 3. If retries remain, it releases the job back to the queue with a delay (based on $backoff array or the driver's retry_after). The 'attempts' counter increments. 4. If no retries remain (attempts >= $tries), the worker moves the job to the failed_jobs table (if that migration was run) and fires the JobFailed event. 5. The worker then loops to the next job. The failed job can be retried later via queue:retry or manually inspected.
  • QHow can you ensure that jobs dispatched inside a database transaction only execute if the transaction commits?SeniorReveal
    Use the after_commit option in your queue connection config. Set 'after_commit' => true in config/queue.php for the connection. Then, when you dispatch a job inside a transaction, the framework will wait for the transaction to commit before actually pushing the job to the queue. If the transaction rolls back, the job is never dispatched. This prevents scenarios where a job processes data that doesn't exist yet because the transaction wasn't committed.

Frequently Asked Questions

What's the difference between a job and an event listener in Laravel?

An event listener runs synchronously in the same process when the event is fired. A job is a self-contained command object that you push onto a queue to run asynchronously. You can dispatch a job from an event listener to offload long-running tasks, but the listener itself is synchronous. If you implement ShouldQueue on your listener, it becomes a job automatically.

Can I run multiple workers on the same queue with different configurations?

Yes. Each worker process runs independently. They can have different --tries, --timeout, --memory settings. However, they share the same queue driver. If two workers with different timeouts pick up a job, the one with the shorter timeout might time out while the other is still processing, causing duplicate execution. Always keep worker configurations consistent for the same queue.

How do I delay a job by a specific amount of time?

Use the delay() method on the job instance: SomeJob::dispatch()->delay(now()->addMinutes(10)). Under the hood, the queue driver stores the job with an available_at timestamp. The worker will not pick it up until that time passes. For Redis, this uses sorted sets with scores equal to the available_at timestamp.

What should I do if a job fails on production and I need to re-run it after fixing the bug?

First, fix the job's code and deploy. Then run php artisan queue:retry all to re-queue all failed jobs. If you need to retry a specific job, use php artisan queue:retry <job_id>. To inspect the failed job's payload (to see the data at the time of failure), query the failed_jobs table or use php artisan queue:failed.

Is it safe to use `dispatchNow()` in production?

dispatchNow() executes the job synchronously in the current process, bypassing the queue. It's useful for testing or when you need the result immediately (e.g., in a queue worker that's about to shut down). In production, avoid it for long-running tasks — it defeats the purpose of queues. Use it sparingly, and only when you're certain the job won't block the request.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousLaravel REST API DevelopmentNext →Laravel Service Container
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged