robotomarvin/symfony-process-manager
A lightweight PHP manager for Symfony Process that simplifies running, tracking, and controlling multiple concurrent processes. Handle output, errors, exit codes, and lifecycle events through a clean, fluent API.
Architecture fit. v0.0.2 is a domain-model change, not a cosmetic config rename. The package is moving from a transport-centric to a consumer-centric architecture: the consumer becomes the unit of scaling, lifecycle accounting, and log correlation, while transport is demoted to a message-flow attribute. That is the right model for anything running an autoscaler (you scale workers, not queues), but it means every artifact that assumed "transport == worker" — config, dashboards, alerts, runbooks — is now coupled to a concept that no longer exists in the same form.
Integration feasibility. The mechanical work is small: rename the root config key, wrap one or more transports under a consumer, re-import the bundled dashboard. The real cost is that the change touches three separate planes at once (application config, metrics contract, alerting/SLOs), and the metrics plane is the expensive one.
Technical risk.
transports: key; a silent ignore in a v0.0.x package would be a production incident waiting to happen.transport from lifecycle counters will not error — PromQL queries and Grafana panels pinned to transport simply return no data. Silent zero-valued dashboards and dead alerts are materially worse than a failed deploy. This is the single highest-risk item in the release.[main, failed] deliberately starves the fallback. Teams will reasonably assume "list = fan-out/fair share" and instead build head-of-line blocking into their worker. The release notes say this plainly, but it is the kind of semantic that gets misread.start() is a genuine fix — empty template variables and Fixed-strategy pools with zero visibility until first tick were real operational blind spots — but it changes scrape-timing expectations and should be validated against existing alert for: durations.Key questions for the maintainer / to resolve in a spike.
transports: root key, or ignore it?{transport} and {consumer} labels, or is this a hard cutover?unmet_demand behave when the primary is saturated and the fallback is idle?worker_busy_workers aggregate across a consumer's transports, and if so does busy mean "busy on any transport"?worker_backoffs_total applied per consumer or per transport when one transport holds a poison message?getReceiverName() stable across retries and redeliveries, so the retained transport label on messages_processed_total is trustworthy?Stack fit. This is an additive worker-layer component, not a queue replacement. If the platform already runs Symfony Messenger under Laravel, this is a config-plus-dashboards upgrade. If the platform is on Laravel's native queue drivers (Redis/SQS/database), this is only relevant if there is an active or planned move to Messenger — evaluate it as part of that decision, not on its own.
Migration path (per environment).
worker_exits_total{consumer, exit_code}.[main, failed]), and give genuinely fair-shared work its own consumer.rate() expressions and thresholds against consumer; re-baseline thresholds since label cardinality and aggregation change the numbers.Compatibility checks before committing. Pin the exact version — a 0.0.x line with a breaking config rename on a point bump carries no upgrade safety. Verify the /metrics endpoint contract (path, port, scrape interval) against existing scrape config, confirm the Prometheus client version, and confirm the new start() gauge emission does not race the first scrape.
Sequencing. Config rename and dashboard swap must land together in the same change window (the label change forces it), but the dashboard should be prepared and validated in staging beforehand. Rollback is a full revert of both config and dashboards — there is no partial state that works. Budget a short dual-dashboard period rather than an instantaneous delete of the old one.
Maintenance. Treat the metrics label contract as the primary coupling surface between this package and the observability stack. Dashboards and alert rules should live in the same repo as the worker config so a future rename is a single PR, not a cross-team scavenger hunt. Expect continued churn: pre-1.0 releases have already shipped one breaking config change, so plan a review per release rather than a set-and-forget upgrade.
Support. On-call runbooks must be rewritten around consumer — the current triage questions ("which transport is backed up?") no longer map to the lifecycle counters. Keep messenger_messages_failed_total{transport, message_class} as the documented escape hatch for transport-level failure visibility; it is now the only place transport survives in the metrics contract. The [worker N consumer-label] log prefix and extra.consumer JSON context are a real support win — log, metric, and config now share one key.
Scaling. Consumer is the unit of scale, which is cleaner for the autoscaler but concentrates risk: a single consumer spanning several transports scales as one pool, so a hot primary and a cold fallback cannot be sized independently. Watch series cardinality — worker_exits_total{consumer, exit_code} multiplies consumers by exit codes, and a consumer-per-transport topology (the fair-sharing pattern) multiplies consumers again. The start() gauge emission is a clear improvement for Fixed-strategy pools and for anything that builds Grafana template variables from the metric store.
Failure modes to instrument against.
transport to consumer aggregation → either flapping or muted alerts in the first days.Ramp-up. The conceptual shift engineers must absorb is consumer-vs-transport, and specifically that priority ordering is a deliberate starvation policy. For a team already on Messenger, this is a half-day of reading plus a migration; for a team new to Messenger, budget two to three days including the metrics model. The improved log context and the bundled dashboard re-import do most of the onboarding work — the dashboard is effectively the documentation for the new model, so re-import it rather than patching the old one.
How can I help you explore Laravel packages today?