Queue Autoscale v3 is here
A predictive scaling rewrite, worker topology with exclusive and grouped queues, and multihost cluster orchestration. Plus the embarrassing bug that has been silently overprovisioning workers by 1000x.
I tagged cboxdk/laravel-queue-autoscale v3 a couple of days ago. It is a meaningful rewrite of the scaling core, a new worker topology model, and proper multihost orchestration. There are breaking changes, an upgrade guide, and one fix that anyone running v2 in production should care about today.
The 1000x bug
Three of the v2 strategies divided avgDuration by 1000 twice. The metrics pipeline already returned the value in seconds. The strategies treated it as milliseconds and converted it again. Net effect: every scaling decision based on those strategies asked for roughly 1000x more workers than needed.
In practice the autoscaler clamped at max_workers and ran flat out, so most users did not notice anything except higher CPU and memory usage than expected. If you are on v2, upgrade. If you cannot upgrade today, set tighter max_workers caps until you can.
One scaling strategy, three signals
v2 had separate predictive and reactive strategies. Picking between them was a footgun. v3 collapses everything into a single HybridStrategy that always runs three calculations and takes the maximum:
Little's Law for steady state.
Backlog drain when the oldest job is approaching SLA.
Arrival-rate forecasting from a linear regression over recent samples.
The forecaster is real OLS regression with an R² confidence score. When the trend has high confidence, it weights heavily into the decision. When the data is noisy, the steady-state and drain calculations dominate. No more guessing which strategy fits which workload.
Two other signals feed the strategy:
Spawn latency. Workers do not appear instantly. The new EMA tracker measures actual time from spawn to first job and compensates the next decision so you scale up earlier on slow hosts.
p95 pickup time. SLA breaches now use a sort-based percentile over a Redis sample window, not an average. Averages hide tail latency, which is exactly the thing an SLA cares about.
There are six built-in profiles bundling sensible defaults: Balanced, Critical, HighVolume, Bursty, Background, and Exclusive. They are ProfileContract implementations, so you can ship your own.
Worker topology
In v2 every queue was a candidate for scaling. v3 introduces three topology concepts to handle the queues that do not fit that model.
Excluded queues use fnmatch globs to keep queues out of discovery and scaling entirely. Useful for queues managed by Horizon, by another scheduler, or that you simply do not want touched.
'excluded_queues' => [
'horizon-*',
'maintenance',
],
Exclusive profile pins a single worker to a queue. No scaling, no parallelism, just one process. The supervisor restarts it if it dies. This is the right pattern for ordered processing, rate-limited external APIs, or anything that breaks under concurrency. Before v3 you had to run these workers outside the autoscaler. Now they live inside it with the same lifecycle and observability as scaled queues.
Groups run one worker process across multiple queues with priority polling. The queues scale together based on aggregated metrics. This matches the typical Laravel setup where you write queue:work redis --queue=high,default,low and want one worker pool draining all three by priority. v2 forced you to model these as separate queues and then explain to yourself why scaling decisions felt off.
Multihost orchestration
The biggest piece. v3 ships a Redis-backed cluster mode where multiple managers coordinate through leader election with lease renewal. Each host publishes a heartbeat with CPU, memory, current worker count, and capacity. The leader makes scaling decisions and distributes them across hosts based on capacity, not raw count.
Practical example: three app servers, one queue, SLA target of 30 seconds. Server A is at 85% CPU. Server B is fresh. The leader spawns the next worker on B even though A asked first. If the leader dies, another manager takes the lease within seconds and continues.
Five new lifecycle events expose all of this so you can hook monitoring, dashboards, and alerts into the cluster state.
Operational details
queue:autoscale:restartfor graceful rolling restarts. No more SIGHUP juggling.queue-autoscale:installis an interactive installer that walks you through queues, profiles, and SLA targets. Less staring at the config file.AlertRateLimitercaps SLA breach notifications so an incident does not flood your Slack channel with one message per evaluation tick.Cookbook recipes for Slack, email, and log alerts. Deployment guides for Forge, Ploi, and Docker.
Upgrading from v2
The config shape changed. Run:
composer require cboxdk/laravel-queue-autoscale:^3.0
php artisan queue-autoscale:migrate-config
The migrator rewrites your existing config to the new structure (sla->targetSeconds, workers->min/max, profile references). PredictiveStrategy is gone, replaced by HybridStrategy. Static ProfilePresets calls become ProfileContract implementations. The TrendScalingPolicy enum is now ForecastPolicyContract classes.
Step-by-step instructions live in docs/upgrade-guide-v3.md.
Tested where it matters
435 tests, 1070 assertions. Green on PHP 8.3, 8.4, and 8.5. Green on Laravel 11, 12, and 13. PHPStan clean. Pint clean. The test suite is on GitHub if you want to see what is actually exercised.
Full release notes: v3.0.0 on GitHub. Package docs: laravel-queue-autoscale.
// Sylvester Damgaard