How does Laravel Horizon tag queued jobs automatically, and what must you schedule so its metrics dashboard shows throughput and runtime graphs?
answer
- tags fixed at dispatch
- Eloquent model properties become Class:id
- a tags() method replaces the automatic ones
- horizon:snapshot every five minutes
- trim_snapshots 24 per job and queue
basics
~10 sHorizon tags a job at dispatch with Class:key for every Eloquent model in its properties, unless the job defines tags(), and its metric graphs fill only when horizon:snapshot is scheduled, typically every five minutes.
solid answer
~40 sWhen a job is pushed onto a Horizon-managed Redis queue, Horizon stores tags in the payload. If the job (or, for mailables, notifications, broadcast events and queued listeners, the wrapped object) has a `tags()` method returning a non-empty array, those are used; otherwise Horizon reflects over its properties and tags each Eloquent model or model collection as `App\Models\Invoice:42`. Tags let you search failed jobs, monitor a tag in the dashboard and hide noisy work with `silenced_tags`. For metrics, workers accumulate throughput and average runtime per job and queue in Redis, but the graphs are drawn from snapshots: schedule `Schedule::command('horizon:snapshot')->everyFiveMinutes()` in `routes/console.php`. `metrics.trim_snapshots` keeps 24 snapshots per job and queue, about two hours at that interval.
code
php · 25 lines<?php
namespace App\Jobs;
use App\Models\Invoice;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class SendInvoiceReminder implements ShouldQueue
{
use Queueable;
public function __construct(public Invoice $invoice) {}
public function tags(): array
{
// replaces the automatic App\Models\Invoice:<id> tag, so keep it
return ['billing', 'invoice:'.$this->invoice->id, 'customer:'.$this->invoice->customer_id];
}
public function handle(): void
{
// ...
}
}go deeper
Know that Horizon tags jobs with their Eloquent models and that metrics need horizon:snapshot on the scheduler.
Explain when tags are computed, how a tags() method replaces automatic tags, and how snapshots and trim_snapshots shape the graphs.
Design a tagging scheme support can search, monitor critical tags, silence noise, and size snapshot retention for incident review.
Decide which queue signals the team watches in Horizon versus a wider monitoring stack, and who owns the thresholds.
## Where tags come from A **tag** in Horizon is a short string attached to a queued job so you can find it later. Horizon computes tags **at dispatch time**, when the job is pushed onto a Horizon-managed Redis queue, and stores them in the job's JSON payload. Tags therefore reflect the job's data at the moment it was queued. Horizon handles more than plain jobs. For framework wrappers it looks inside at the object that carries the data: - a queued **mailable** - the mailable; - a queued **notification** - the notification; - a **broadcast event** - the event; - a **queued event listener** - both the listener and the event it received. ## Automatic tags If no explicit tags are found, Horizon uses reflection to read every property of the target object and collects: - each property holding an Eloquent **model**, tagged as the model's class name and primary key, e.g. `App\Models\Invoice:42`; - each property holding an Eloquent **collection**, one tag per model in it. A job built as `new SendInvoiceReminder($invoice)` therefore carries the tag `App\Models\Invoice:42` without any code, so a failed reminder can be found by invoice, which is exactly what support needs when a customer asks why an email never arrived. ## Manual tags Define a `tags()` method returning an array of strings to choose the tags yourself: 1. On a job, mailable or notification, `tags()` needs no parameters. 2. On a queued listener, Horizon passes the event instance, so the listener can tag with event data. 3. If `tags()` returns a non-empty array, those tags **replace** the automatic model tags; include the model tag yourself if you still want it. ## What tags are used for | Feature | How tags are involved | |---|---| | Search | the failed-jobs screen filters by tag | | Monitoring | once you monitor a tag, jobs pushed with it are recorded and listed for `trim.monitored` minutes (10080 published) | | Silencing | jobs carrying a tag in `silenced_tags` are left out of the completed-jobs list | | Failed jobs | failed jobs are indexed by their tags so you can find all failures for one customer | ## Metrics and snapshots Horizon's **Metrics** screen shows throughput (jobs processed) and average runtime per job class and per queue, plus queue wait time. Two separate mechanisms produce it: 1. **Counters.** Each time a worker finishes a job, Horizon updates a Redis hash for that job class and that queue: the processed count and a running average of runtime. 2. **Snapshots.** `php artisan horizon:snapshot` reads those counters, stores a timestamped snapshot for the graphs, and **resets** the counters for the next interval. It takes a short lock, so running it from several servers does not double-count. Without the second step the graphs stay empty. The docs ask you to schedule it every five minutes in `routes/console.php`: ```php use Illuminate\Support\Facades\Schedule; Schedule::command('horizon:snapshot')->everyFiveMinutes(); ``` `metrics.trim_snapshots` in `config/horizon.php` keeps **24** snapshots for jobs and 24 for queues. Retention is a count, not a duration: at five-minute snapshots that is about two hours of history; snapshot hourly and the same setting keeps a day. `php artisan horizon:clear-metrics` deletes the stored metrics. ## Silencing noisy jobs Silencing uses the same machinery. Jobs are silenced, meaning kept out of the completed-jobs list, when: - their class is listed in the `silenced` option; - they carry a tag listed in `silenced_tags`; - the class implements `Laravel\Horizon\Contracts\Silenced`. Silencing only moves them from the completed list to a separate silenced list; the jobs still run, still count in metrics, and still appear under failed jobs if they fail. ## Wait-time alerts Related to metrics, the `waits` option sets per `connection:queue` how many seconds a job may wait before Horizon fires `LongWaitDetected` (published: `'redis:default' => 60`; combinations you leave out also use 60; `0` disables the alert). Route the resulting notifications with `Horizon::routeMailNotificationsTo()` or the Slack and SMS equivalents in your `HorizonServiceProvider`. ## Common mistakes - Expecting a tag computed from data that changes after dispatch. - Adding a `tags()` method and losing the automatic model tags. - Installing Horizon and never scheduling `horizon:snapshot`, then assuming metrics are broken.
- Snapshots run every five minutes and trim_snapshots is 24. Someone wants a day of graphs. What are the options?Retention is a count of snapshots, so either raise `metrics.trim_snapshots.job` and `.queue` to 288 for a day at five-minute intervals, or run `horizon:snapshot` hourly and keep 24. More snapshots cost Redis memory; a coarser interval loses resolution.
- How would support find every queued and failed job for one customer?Tag the jobs with a customer identifier, for example by returning `'customer:'.$id` from `tags()`, or rely on the automatic model tag if the customer model is a job property. Search failed jobs by that tag, and start monitoring the tag so jobs pushed with it from then on are recorded and listed for the `trim.monitored` period.
saying these in an interview costs you the question
- Horizon computes tags when the worker picks the job up
- A tags() method adds to the automatic model tags
- Horizon's metric graphs fill in without any scheduled command
- trim_snapshots is a retention period measured in hours
- Automatic tags only work for public model properties set by hand