Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Meridian Laravel Package

iso-doss/laravel-meridian

Laravel Meridian is a lightweight Laravel package from iso-doss that provides a clean, configurable integration for adding Meridian functionality to your app, with a developer-friendly API, sensible defaults, and minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Meridian is a Horizon replacement aimed at database queues, where Horizon (Redis-only) is not an option. The mental model: it listens to Laravel's queue events to write a job history table, and separately reads the jobs table to catch work that never fired an event. If you're on QUEUE_CONNECTION=database and have been debugging with select * from jobs and failed_jobs, this is the dashboard you were missing.

Minimum path:

composer require iso-doss/laravel-meridian
php artisan meridian:install   # publishes config + migrations, runs them
php artisan migrate

Then visit the configured prefix (default web routes). Before anything renders you must define the gate:

// app/Providers/AppServiceProvider.php
Gate::define('viewMeridian', fn ($user) => $user?->isAdmin());

First use case: push a single job, run php artisan queue:work --queue=default, and confirm it appears in the jobs list with status transitions queued → processing → completed. If it doesn't, your meridian config doesn't list that connection as a database queue — fix that before looking anywhere else. Second use case: open the workload view and confirm your worker shows up as a heartbeat without having started Meridian's supervisor.

When you're ready to try the supervisor, do it locally first with php artisan meridian:listen. It runs Meridian and restarts it whenever a PHP file in the directories listed in the new watch config option changes — polling, so unlike horizon:listen it needs no Node and no npm packages. --poll= sets the interval in seconds; cache, vendor and storage are skipped automatically.

Implementation Patterns

Separate the three planes. Meridian gives you (a) observability from events, (b) reconciliation from the jobs table, (c) process supervision. Teams get burned by conflating them. Day-to-day: use the dashboard for triage, the JSON API for alerting/automation, and the supervisor only where you'd otherwise hand-roll a supervisord block.

Supervisor in prod, meridian:listen locally. In production, run php artisan meridian under systemd or Supervisord as a single long-lived process — it forks queue:work children. Locally, use meridian:listen (auto-restart on file change, no Node toolchain) or plain queue:work if you only want heartbeats in the UI. Never run both php artisan meridian and a stray queue:work on the same queue in production; the unmanaged worker won't respond to meridian:pause / meridian:terminate and you'll chase phantom capacity.

Deploy restart is mandatory. Database workers hold code in memory. Standard deploy hook:

php artisan meridian:terminate   # or queue:restart if not using the supervisor
php artisan meridian           # systemd restarts it for you

Size your auto-scaling strategy to the workload. The new log strategy ('autoScalingStrategy' => 'log') distributes processes by the logarithm of each queue's pending count, so one enormous queue can't monopolise the whole pool. Use log when you have a few firehose queues alongside small ones; stick with the linear/depth-based behaviour when queue depths are broadly comparable and you want proportional capacity.

Handle the memory-limit exit code. The master supervisor now enforces memory_limit (64 MB default, 0 disables). On breach it drains running jobs and exits 12, firing MasterSupervisorOutOfMemory. Your process monitor must treat 12 as a restart, not a crash: Restart=always in systemd, autorestart=true in Supervisord. If you supervise with something that gives up on non-zero exits, your workers die quietly overnight.

Tag for searchability. Search by class/UUID is free; tags are what make the dashboard usable at volume. Attach them on the job ($this->tags = ['tenant:'.$id, 'billing']) so the jobs list is filterable by domain concern rather than fully-qualified class name.

Use silencing for known-noisy jobs. Implementing the Silenced contract (or setting $silenced / $silencedTags) moves a job out of « Completed » into its own tab without losing the record — useful for high-frequency polling or heartbeat jobs that would otherwise drown the list. Silenced jobs are retained (status silenced) until meridian:prune, and they now count in the metrics.

Cancel vs retry — know the semantics. Cancel can only remove a job that hasn't been reserved; an in-flight job will finish (or fail) regardless. Retry re-dispatches from the stored payload — for jobs with side effects, make sure the handler is idempotent. This is the same contract as Horizon, but on a database queue the payload is durable, so a retry genuinely replays the original arguments.

Tune the scheduler, don't ignore it. meridian:snapshot and meridian:prune are auto-registered, which means the throughput chart's granularity is set by snapshot cadence and table growth is set by prune retention. If schedule:run isn't actually cron'd on that host, you get an empty chart and an unbounded history table — a silent double failure. Prune retention now governs silenced rows too.

Ops API. Point your external monitor at the stats/workload endpoints rather than scraping Blade. Put real auth in meridian.route.api.middleware (auth:sanctum, an IP allowlist, whatever fits) — the gate protects the web UI, not necessarily the API surface.

Gotchas and Tips

  • The gate is not optional. Ship Gate::define('viewMeridian', ...) in the same PR as the composer require, or every user gets 403 and it looks broken.
  • Upgrading to 1.6.0 changes long-wait alerting behaviour. A queue not listed in waits previously fell back to nothing; it now uses '*', or 60 seconds when '*' isn't set, matching Horizon. Set 0 to explicitly disable a queue. Expect a wave of LongWaitDetected notifications on upgrade if your waits map was sparse — audit it before deploying, and remember notifications only fire when routes are configured.
  • Payloads are stored. Full job payloads and exception traces land in your database, unredacted. That means secrets passed as job arguments and PII in exception messages are now queryable by anyone with DB access. Restrict access to the Meridian tables, keep retention tight, and stop passing credentials as constructor args.
  • Two sources of truth → duplicates. Jobs recorded by events and present in the jobs table are reconciled by UUID; verify the unique index exists after migrating. Jobs from other applications sharing the database have no events, so they'll only appear after reconciliation — expect lag, and expect null wait/runtime metrics for them.
  • Imported jobs have incomplete metrics. Wait time and runtime require a queued and a processing event. Bulk-inserted and pushRaw jobs get history but sparse timing; don't build an SLO dashboard on columns that are null for half your rows.
  • pcntl/posix requirement. php artisan meridian will not run on Windows, on minimal containers, or where those extensions are disabled. Check with php -m before promising the supervisor to anyone. The dashboard and heartbeats work fine without them.
  • Long-wait notifications are keyed per connection:queue. A typo in the key silently disables alerting — no error, no mail, no event. Verify by deliberately clogging a queue in staging and watching for LongWaitDetected.
  • Config changes need a route cache clear. Namespace, prefix, domain and middleware live in config; if you deploy with route:cache, run route:clear as part of the change or your new prefix won't exist.
  • Row volume on the history table — and it just grew. Database queues already write jobs rows; Meridian adds a history row plus transitions per job, and silenced jobs are no longer deleted on completion. At high throughput this table outgrows everything else. Watch index size, not just row count, and set separate retention expectations for completed, silenced and failed jobs.
  • Balancing modes change capacity silently. auto scaling responds to queue depth — a burst can spawn workers your DB connection pool or CPU budget can't absorb, and the log strategy only softens the skew, it doesn't impose a ceiling. Cap worker counts in config before turning on auto, and rehearse meridian:pause / meridian:continue so you know what a paused pool looks like in the UI.
  • Publishing config or views means you own the upgrade. The 1.6.0 upgrade needs no migration, but if you published config/meridian.php add the new memory_limit and watch keys (defaults apply when absent), and if you published the Blade views you must republish them to get the « Silenced » tab.
  • meridian:listen is dev-only. It polls the filesystem and restarts the whole supervisor on any watched PHP change — never point it at a production host, and keep watch narrow (skip generated directories, which it already does for cache/vendor/storage).
  • Version floor. Laravel 12/13 and PHP 8.3+ only. Not a candidate for a mixed-version estate; scope it to the apps that qualify.
  • Extension point: the LongWaitDetected and MasterSupervisorOutOfMemory events are the cleanest hooks for routing alerts into Slack/PagerDuty rather than relying on the built-in mail notification — the out-of-memory one is worth wiring even if you restart automatically, so you can tune the 64 MB default instead of restarting your way through a memory leak.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource