Laravel Queue Worker & Scheduler Updates in v13
Laravel 13 introduces refined queue worker resource controls and task scheduling updates, including percentage-based memory caps (queue:work --memory), pre-timeout job notifications, global single-server schedule definitions (Schedule::alwaysOnOneServer()), and queue-specific failed job clearing. This guide covers how to implement these capabilities to make background processes more stable and predictable across deployment environments.
Overview of Queue Worker & Scheduler Updates
Managing background queue workers and scheduled tasks in production requires balancing resource limits, task duration, and server synchronization. Recent releases in the Laravel 13 framework line—specifically Laravel v13.33.0, Laravel v13.34.0, and Laravel v13.35.0—address core execution challenges in worker runtime monitoring and schedule configuration.
Key additions include:
- Dynamic Worker Memory Limits: Worker memory limits can now be declared as percentage values (for example,
--memory=80%) rather than fixed megabyte values. - Pre-Timeout Job Notifications: Jobs can receive signal notifications when approaching timeout thresholds, allowing graceful cleanup before process termination.
- Opt-In Worker Timeout Survival: Option to prevent worker process destruction when an individual job times out.
- Targeted Failed Queue Flushing: The
queue:flushcommand accepts a--queue=option to clear failed jobs selectively per queue. - Global Single-Server Scheduling:
Schedule::alwaysOnOneServer()enforces single-server execution globally across scheduled jobs. - Enhanced Scheduler Telemetry:
schedule:list --jsonoutput includes theonOneServerattribute, and long-running scheduled commands can listen forschedule:interrupt. - Job Event Metrics:
JobProcessedevents now carry job execution duration metrics.
Who Needs These Changes and What Problems They Solve
1. Multi-Tier Container and Cloud Hosting Teams
In containerized environments (such as Docker, Kubernetes, or AWS ECS), container memory limits vary by deployment environment. Previously, specifying --memory=128 meant updating deployment configurations whenever container allocations changed. Passing --memory=80% allows queue processes to adapt automatically to host or container memory limits.
2. Applications Running Long-Running Batch Jobs
When processing video rendering, PDF generation, or third-party API batch syncs, unexpected job timeouts normally force the worker to terminate abruptly via OS signals. Jobs can now capture pending timeout signals to roll back transactions, close network sockets, or mark job states before termination occurs.
3. Multi-Server Distributed Deployments
Deploying web servers behind load balancers requires scheduled tasks to run on only one instance to prevent duplicate job dispatching. Previously, developers had to append ->onOneServer() to every scheduled item in routes/console.php. The global directive enforces this rule by default across all scheduled commands.
Feature Comparison: Older Approaches vs. Laravel 13
| Feature Focus | Older Approach (Laravel 12 & Earlier) | Laravel 13 Approach | Practical Benefit |
|---|---|---|---|
| Worker Memory Limits | Fixed MB integers only (e.g., --memory=128) |
Fixed MB integers or percentages (e.g., --memory=75%) |
Prevents OOM kills when scaling container RAM without modifying artisan scripts. |
| Job Timeout Notification | Worker terminated job via SIGALRM without notifying application logic | notifyJobOfSignal dispatches notification to job before exit |
Allows explicit state updates, database rollbacks, or alert logging. |
| Failed Queue Flushing | queue:flush cleared all failed jobs across every queue |
queue:flush --queue=high-priority |
Prevents accidental deletion of failed jobs belonging to unrelated business queues. |
| Single Server Scheduling | Configured per individual task: $schedule->command(...)->onOneServer() |
Set globally: Schedule::alwaysOnOneServer() |
Eliminates duplicate execution risks when developers forget per-task configuration. |
| Scheduler Inspection | schedule:list --json only showed command strings and cron expressions |
Included onOneServer boolean state |
Enables automated CI/CD checks for schedule server lock settings. |
Practical Guide: Implementing the New Background Worker Features
Step 1: Upgrading Worker Process Supervisors
If you run queue workers under Supervisor or Systemd, update your worker startup commands to leverage adaptive memory limits. When running web apps on production hosts managed by process managers alongside NGINX Explained for Beginners, ensuring background tasks do not exhaust system memory is crucial.
Update your /etc/supervisor/conf.d/laravel-worker.conf configuration file:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --memory=80%
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600
When queue:work runs with --memory=80%, Laravel evaluates ini_get('memory_limit'). If the PHP CLI memory limit is set to 512M, the worker automatically restarts upon reaching approximately 409.6M of RAM usage.
# Reload Supervisor configuration in production
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart laravel-worker:*
Step 2: Graceful Job Handling Prior to Timeout
Laravel 13 adds timeout notification support to queued job instances. When a job approaches its designated --timeout limit, a system signal (such as SIGALRM on POSIX systems) triggers a job hook if implemented.
Create a resilient export job class (app/Jobs/GeneratePdfReport.php):
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class GeneratePdfReport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $timeout = 120; // 2 minutes timeout
public string $tempFilePath;
public function __construct(public int $reportId)
{
$this->tempFilePath = "tmp/report_{$reportId}.tmp";
}
public function handle(): void
{
Log::info("Starting PDF report generation for ID: {$this->reportId}");
// Simulate step-by-step heavy processing
for ($step = 1; $step <= 100; $step++) {
$this->processChunk($step);
usleep(1000000); // 1 second delay per chunk
}
}
/**
* Hook invoked when worker sends a timeout warning signal.
*/
public function timeoutAt(): void
{
Log::warning("Job ID {$this->reportId} is approaching timeout threshold. Cleaning temporary files.");
if (Storage::disk('local')->exists($this->tempFilePath)) {
Storage::disk('local')->delete($this->tempFilePath);
}
}
private function processChunk(int $step): void
{
Storage::disk('local')->append($this->tempFilePath, "Completed chunk {$step}");
}
}
Step 3: Granular Failed Queue Operations
In high-volume applications, a bug in an isolated secondary queue (such as webhook deliveries) can generate thousands of failed entries. Previously, running artisan queue:flush deleted all failed records across all queues.
Laravel 13 adds a --queue= option to isolate operations:
# Inspect failed jobs before flushing
php artisan queue:failed
To delete failed entries originating exclusively from the webhooks queue while keeping payments or notifications queues intact:
php artisan queue:flush --queue=webhooks
You can verify the flush output in your terminal logs:
Flushing failed jobs for queue [webhooks]...
Flushed 42 failed jobs successfully.
Step 4: Configuring Global Single-Server Task Scheduling
When managing multi-node application clusters, define your global scheduler configuration inside routes/console.php or within a service provider (app/Providers/AppServiceProvider.php).
Edit routes/console.php:
<?php
use Illuminate\Support\Facades\Schedule;
// Force all scheduled commands in this application to run on one server only
Schedule::alwaysOnOneServer();
Schedule::command('reports:daily')
->dailyAt('01:00');
Schedule::command('cache:prune-stale-tags')
->hourly();
Schedule::command('sync:inventory')
->everyFiveMinutes();
To inspect schedule properties programmatically or in continuous integration (CI) environments, request JSON output:
php artisan schedule:list --json
Example JSON output:
[
{
"command": "php artisan reports:daily",
"expression": "0 1 * * *",
"timezone": "UTC",
"user": null,
"maint_mode": false,
"onOneServer": true,
"behindOnOneServer": false
},
{
"command": "php artisan cache:prune-stale-tags",
"expression": "0 * * * *",
"timezone": "UTC",
"user": null,
"maint_mode": false,
"onOneServer": true,
"behindOnOneServer": false
}
]
Step 5: Handling Interrupt Signals in Scheduled Commands
Long-running scheduled maintenance tasks can now handle cancellation signals during application deployments or queue pauses using schedule:interrupt detection.
Create a console command (app/Console/Commands/ReindexSearchIndex.php):
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
class ReindexSearchIndex extends Command
{
protected $signature = 'search:reindex';
protected $description = 'Rebuild search index in batches with interrupt handling';
public function handle(): int
{
$this->info('Starting search reindex...');
$totalBatches = 50;
for ($i = 1; $i <= $totalBatches; $i++) {
// Check if schedule interrupt signal or cache flag was dispatched
if (Cache::has('schedule:interrupt')) {
$this->warn('Interrupt signal detected. Stopping reindex cleanly at batch ' . $i);
return Command::SUCCESS;
}
$this->performReindexingBatch($i);
usleep(500000);
}
$this->info('Search reindex completed successfully.');
return Command::SUCCESS;
}
private function performReindexingBatch(int $batchNumber): void
{
// Perform batch operations
}
}
Troubleshooting & Common Edge Cases
1. Worker Percentage Memory Calculation Fails
- Symptom:
queue:work --memory=80%exits immediately or throws an exception regarding invalid memory limit. - Cause: The PHP CLI environment has
memory_limit = -1set inphp.ini(unlimited memory). - Fix: Provide a explicit CLI memory limit in
php.inior set it inline when starting the worker process:
php -d memory_limit=512M artisan queue:work --memory=80%
2. Job Timeout Notifications Not Firing (SIGALRM missing)
- Symptom:
timeoutAt()lifecycle hook is not executed before the job times out. - Cause: The
pcntlextension is either disabled or running under a Windows host environment, where POSIX process signals are unavailable. - Fix: Ensure
pcntlis enabled in your Linux/macOS PHP CLI environment:
php -m | grep pcntl
If missing, install or enable ext-pcntl in your Dockerfile or server environment:
RUN docker-php-ext-install pcntl
3. alwaysOnOneServer() Cache Mutex Issues
- Symptom: Scheduled tasks do not run on any server after configuring
Schedule::alwaysOnOneServer(). - Cause: Single-server scheduling relies on a central default cache store (e.g., Redis or Memcached). If servers use localized
fileorarraycache drivers, each server believes it owns the lock or fails to acquire a shared lock key. - Fix: Verify
CACHE_STOREin.envresolves to a shared central driver across all cluster nodes:
CACHE_STORE=redis
Verification and Testing Steps
To confirm your queue worker runtime and scheduler features operate correctly:
-
Validate Memory Limit Dynamic Triggers: Run a queue worker in verbose mode with low memory allocation:
php -d memory_limit=128M artisan queue:work --memory=50% -vDispatch jobs that consume memory and confirm worker restarts when memory usage passes ~64 MB.
-
Verify Job Processed Telemetry: Listen for
JobProcessedevents in your application code or automated tests:use Illuminate\Queue\Events\JobProcessed; use Illuminate\Support\Facades\Event; Event::listen(JobProcessed::class, function (JobProcessed $event) { Log::info("Job {$event->job->resolveName()} processed in {$event->duration} ms"); }); -
Test Single-Server Schedule Lock: Run
php artisan schedule:list --jsonin your build step to assert that `
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System with Admin Panel - Kritim Yantra
Flask Auth Pro with Admin PanelA complete Flask authentication and user management system built for developers who want...
Flask Authentication System (Free Version) - Kritim Yantra
🛠 Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.