[ GUIDE ]

Monitor Laravel queue latency

The time from dispatch to start is the signal that matters most. Here's how to measure, trend, and alert on it.

QUICK ANSWER

How do I monitor Laravel queue latency?

Record timestamps at job dispatch and at worker start; the delta is queue latency. Aggregate p95 per queue over time to trend lag, and alert when a queue exceeds its SLO for 5+ consecutive minutes. Laravel doesn't track this by default — you need custom middleware or an APM. NightOwl records dispatched_at and started_at for every job with per-queue p95 trending and SLO-based alerts.

Updated · 2026-04-13

Latency vs runtime — the two halves

Perceived job delay = queue latency + job runtime. They're separate problems:

Problem Cause Fix
High queue latencyNot enough worker capacity for dispatch rateScale workers or split queues
High runtimeThe job itself is slow (DB, external API, computation)Optimize or decompose the job

Measuring latency yourself

Laravel fires JobProcessing when a worker picks up a job. The job's payload includes the dispatch timestamp in pushedAt:

app/Providers/EventServiceProvider.php

php
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;

Event::listen(function (JobProcessing $event) {
    $payload = $event->job->payload();
    $pushedAt = $payload['pushedAt'] ?? null;

    if (!$pushedAt) return;

    $latencyMs = (microtime(true) - $pushedAt) * 1000;

    DB::table('queue_latency')->insert([
        'queue' => $event->job->getQueue(),
        'job_class' => $event->job->resolveName(),
        'latency_ms' => $latencyMs,
        'started_at' => now(),
    ]);
});

Aggregate with percentile_cont(0.95) WITHIN GROUP (ORDER BY latency_ms). Roll up per queue per minute.

Per-queue SLOs

Assign each queue a latency target based on what it serves:

Queue Work type SLO (p95)
transactionalPassword resets, 2FA codes< 3s
defaultUser notifications, light background work< 15s
indexingSearch index updates< 60s
batchNightly exports, bulk imports< 10m

Separate fast from slow

Mixing fast and slow jobs on the same queue with the same workers is the number-one cause of latency spikes. A 5-second job class that fires 100 times floods the queue and starves the sub-second email jobs behind it. Fix by splitting queues and dedicating workers:

app/Jobs/SendPasswordResetEmail.php

php
public $queue = 'transactional';

app/Jobs/GenerateMonthlyReport.php

php
public $queue = 'batch';

supervisord config

ini
[program:laravel-transactional-worker]
command=php artisan queue:work --queue=transactional --sleep=0 --tries=3
numprocs=5

[program:laravel-batch-worker]
command=php artisan queue:work --queue=batch --sleep=3 --timeout=600 --tries=1
numprocs=2

Alerting on backlog

Alert on age-of-oldest-pending-job rather than count:

php
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\DB;

// For database driver
$oldestPendingAge = DB::table('jobs')
    ->where('queue', 'transactional')
    ->min('created_at');

if ($oldestPendingAge && now()->diffInSeconds($oldestPendingAge) > 10) {
    alert('Transactional queue lag > 10s');
}

THE EASY WAY

NightOwl records queue latency per queue with p95 trending

Per-queue dashboards show p95 latency over any time range, separate from job runtime. Set per-queue SLOs and get alerted when p95 exceeds them for 5+ minutes. Works with Redis, database, SQS, Beanstalkd.

bash
composer require nightowl/agent
php artisan nightowl:install

From $5/month flat. Data in your PostgreSQL.

Frequently asked questions

What is queue latency in Laravel?

The time between when a job is dispatched and when a worker actually starts processing it. Also called queue wait time or time-to-start. Distinct from job runtime (how long handle() takes once it starts). High queue latency means dispatched jobs pile up waiting for worker capacity — users see delayed emails, outdated search indexes, unposted webhooks.

How do I measure Laravel queue latency?

Capture timestamp at dispatch (dispatched_at) and at job start. The delta is latency. Laravel's queue system doesn't track this by default — you need either custom middleware that records both timestamps or an APM that does it. NightOwl records dispatched_at and started_at for every job attempt and exposes per-queue lag trending.

What's a healthy Laravel queue latency?

Depends on the queue. User-facing work (password reset email, transactional mail) should be under 5 seconds p95 end-to-end. Background work (index updates, report generation) tolerates 30-60 seconds. Batch work (overnight sync) can tolerate minutes. Set a per-queue SLO and alert when it's exceeded for 5+ consecutive minutes.

Why is my Laravel queue lagging?

Four common causes: (1) not enough workers for current dispatch rate — scale up workers, (2) one job class with a long runtime blocking a shared queue — split it to its own queue, (3) a downstream dependency failing and triggering retries — releases eat worker capacity, (4) worker crashes not getting supervised correctly — check Supervisor/systemd.

Should I use separate Laravel queues for fast and slow jobs?

Yes, almost always. Put fast user-facing work (emails, notifications) on a dedicated 'default' queue and slow background work (reports, bulk updates) on 'background'. Run separate workers per queue so a long job class never starves the fast queue. Set different timeouts and retry strategies per queue.

How does queue latency differ from job runtime?

Queue latency = dispatched_at to started_at. Job runtime = started_at to completed_at. Total perceived latency = latency + runtime. Users only care about total; you need to distinguish because the fixes are different. High latency = more workers. High runtime = optimize the job.

Does Horizon show queue latency?

Horizon's 'Pending' view shows current wait time for the oldest job per queue — a point-in-time snapshot. For historical latency trending (p95 latency over the last 24 hours) you need something that records per-job timestamps. APMs like NightOwl do this automatically.

How do I alert on queue backlog growth?

Two approaches. Count-based: alert when Queue::size('default') exceeds a threshold for 5+ minutes. Time-based: alert when the oldest pending job's age exceeds your SLO. Time-based is usually better because a tiny backlog of 10 jobs on a slow queue may still exceed SLO, while 10K jobs on a fast queue may be fine. NightOwl's queue metrics support both.

PRICING

Flat pricing. No event caps. No per-seat fees.

14-day free trial, no credit card. Your PostgreSQL, your data.

HOBBY

$5 /month

1 app · 14 days lookback · all Laravel events

TEAM

$15 /month

Up to 3 connected apps · unlimited environments · all Laravel events

AGENCY

$69 /month

Unlimited apps · unlimited agent instances · same flat rate at any traffic

Related