krak/symfony-messenger-auto-scale
Automatically scale Symfony Messenger workers based on queue load, so you run only as many consumers as needed and save resources during quiet periods. A lightweight package for dynamic worker management.
composer require krak/symfony-messenger-auto-scale:^0.1.4
The package is a thin supervisor around messenger:consume: instead of a fixed numprocs=N in Supervisor, you run one scaler process per transport, and it spawns/terminates consumer child processes based on how many messages are actually sitting in the queue.
What v0.1.4 changes for you: the release notes are one line — "Support symfony 5 and 6." It's a compatibility widening, not a feature release. Nothing in the option names, YAML shape, command signature, or scaling semantics changed, so a 0.1.3 → 0.1.4 bump is operationally a no-op provided you're on Symfony 5.4 or 6.x. The flip side matters more: Symfony 7 is still not supported, so a Symfony 7 app cannot resolve this package at all. Treat it as "this pins your consumer sidecar to 6.4 LTS," and plan the Symfony 7 migration as a separate decision.
Where to look first
vendor/krak/symfony-messenger-auto-scale/composer.json — confirm the actual symfony/messenger constraint before you plan an upgrade. "Supports 5 and 6" may mean ^5.0 || ^6.0 (which would let you run EOL 5.0–5.3) or ^5.4 || ^6.0; only the file knows.vendor/krak/symfony-messenger-auto-scale/src/ — identify the console command (bin/console list messenger will show it; bin/console messenger:auto-scale --help for its args) and the bundle class. If Flex didn't register it, add it to config/bundles.php.*.yaml in vendor/.../config/ or a DependencyInjection/Configuration.php — that file is the authoritative source for option names, defaults, and whether workers are configured per-transport or globally.README.md for the Supervisor snippet the author intended; copy it rather than inventing your own process-group settings.If your day job is Laravel: this is a Symfony package, and Laravel already ships the equivalent — Horizon's balance: auto with minProcesses/maxProcesses, or queue:work under Supervisor with --max-jobs/--max-time. Reach for this one only in repos that genuinely run Symfony Messenger (mixed stack, shared internal bundle, Symfony sidecar consuming the same Redis/AMQP). Don't try to point it at Laravel queues; it reads MessageCountAwareInterface, not the Laravel queue API.
First use case
Point it at a bursty transport you'd normally over-provision — webhook delivery, image thumbnailing, PDF generation, outbound email — something that is idle overnight and backs up at 9am. Keep your latency-critical or low-volume queues on plain messenger:consume under Supervisor; migrating those gains nothing and adds a moving part.
Minimal shape (verify key names against the package's Configuration.php):
# config/packages/messenger_auto_scale.yaml
messenger_auto_scale:
transports:
async_burst:
min_workers: 1
max_workers: 16
; supervisor: ONE process for the scaler, not N consumers
[program:messenger-autoscale]
command=php %(ENV_APP_ROOT)s/bin/console messenger:auto-scale async_burst --env=prod
numprocs=1
stopasgroup=true
killasgroup=true
stopwaitsecs=60
autorestart=true
Replace numprocs, don't stack on top of it. The scaler owns child lifecycle. Running both a static numprocs=8 consumer group and the scaler on the same transport means you're double-consuming and the scaler can never reach min_workers=0.
Fast up, slow down. Whatever the package calls it — cooldown, scale-down delay, interval — tune the two directions asymmetrically. Scale-up can react within a poll or two; scale-down should require sustained emptiness (30–60s) so you don't kill a worker mid-burst just because the queue drained momentarily.
Let children recycle themselves. Pass --time-limit=3600 --memory-limit=128M through to the consumers so long-lived workers exit cleanly and get replaced — this is the standard Messenger memory-leak defence and it composes well with a scaler that is already restarting processes. Both flags exist on messenger:consume across the 5.4 and 6.4 range v0.1.4 targets, so the same command line works whichever LTS you're on. If the scaler doesn't accept pass-through args, wrap messenger:consume in a two-line shell script and point command-style config at that.
One scaler per transport group. Don't ask a single process to manage every transport in the app; you want independent failure domains and independent min/max tuning (a min_workers: 0 queue should be able to sleep while a min_workers: 2 queue stays hot).
In Kubernetes, skip it. The scaler is a single-host Supervisor substitute. On K8s use KEDA's queue-length trigger against the same transports and let the Deployment controller do the work — you get the metrics, health checks, and rolling restarts for free. Use this package where you actually have Supervisor/systemd (single VPS, bare metal, some ECS-on-EC2 setups).
Observability: wrap the scaler's log output with a Monolog channel, and emit a gauge (current worker count per transport) to StatsD/Prometheus on each scaling decision. Without that gauge the scaler is a black box — you can't tell "scale-up is working" from "we're just permanently pinned at max_workers."
Testing the decision logic: the scaling policy is pure arithmetic on (messageCount, currentWorkers, elapsed). If the package exposes the policy as a service or interface, unit-test it with a fake count instead of trying to integration-test forked processes. v0.1.4 adds no such interface, so if it wasn't there before, assume it still isn't.
composer update on an app bumping to 7.x will fail resolution on this package. Decide now: stay on 6.4 LTS for the affected service, fork the constraint and test, or replace the package. Don't discover this mid-upgrade.^5.4, your own composer.json should still hold you at 5.4 (or, better, 6.4). Symfony 6 also raises your PHP floor to 8.1 — relevant if the same host runs older PHP services.TransportInterface::getMessageCount() — only transports implementing MessageCountAwareInterface work. Doctrine and Redis are fine. Amazon SQS returns an approximate count and costs an API call per poll. Beanstalkd is approximate. sync:// and null:// cannot be counted at all and will silently never scale.prefetch_count=10 and 5 workers, the broker hands 50 messages to clients immediately, so getMessageCount() reads ~0 while your workers are 100% saturated. Auto-scaling on queue depth is wrong for AMQP-with-prefetch; use it on Doctrine/Redis transports, or keep AMQP prefetch low and accept that scaling reacts to broker-visible backlog only.getMessageCount() is not free. Every poll is a SELECT COUNT(*) on the messenger table (or a Redis LLEN, or an SQS API round-trip). A 1-second interval across several transports is meaningful DB load. Set the poll interval to 5–15s in production.stopasgroup=true + killasgroup=true in Supervisor is not optional; also verify the package forwards SIGTERM to children rather than exiting immediately.proc_open/pcntl means no Windows, and it cannot be driven from a web request or a short-lived command — it's a foregone-conclusion long-running process.min_workers for your baseline traffic, not for zero."krak/symfony-messenger-auto-scale": "0.1.4") so a future tag can't silently change consumer behaviour, and keep the ~100-line custom command wrapping messenger:consume in your back pocket; it's the only part you can't easily replace.ScalePolicy/Scaler interface (swap in a latency-based or rate-based policy instead of raw queue depth) and the worker-pool/process abstraction (swap proc_open for Symfony Process). v0.1.4 doesn't advertise either, so treat the package as a black box and evaluate it purely on the command's operational behaviour.How can I help you explore Laravel packages today?