Skip to content

Laravel Pulse in Production: What I Use, What I Turned Off

Laravel Pulse in Production: What I Use, What I Turned Off

Confession: I avoided Laravel Pulse for the first six months it was out. The dashboard screenshots looked great, but I already had Laravel Telescope wired up and a Grafana board that nobody ever opened. Adding another monitoring tool felt like the project equivalent of buying another notebook to fix the fact that I never write in any of them.

Then the support inbox started filling up with “the app is slow on Tuesdays” and I had no good way to point at why. Telescope is great for one request, useless for trends. Grafana told me CPU was fine. I needed something between those two.

Pulse turned out to be exactly that. I wired it into a Laravel 12 app on a small Hetzner VPS and let it run for six weeks. Some of it was boring. A few bits caught real problems, and a couple of defaults needed disabling before they bit me. Here’s what actually happened.

What Pulse actually is (the thing the readme glosses over)

Pulse is a first-party performance dashboard that lives inside your Laravel app. It records slow queries, slow jobs, slow requests, exceptions, cache hit ratios, queue throughput, and a few other things. Everything goes into your database (you can use a separate connection, more on that), and the dashboard renders at /pulse. The official docs cover install in five minutes.

The bit I missed for a while: Pulse isn’t really a logging tool. It’s a sampling tool. Every record it stores has been aggregated into rolling buckets. So when you look at “slow queries” you’re not seeing every slow query, you’re seeing the top N by aggregated execution time over the window you picked. That’s why the dashboard stays fast even when your app doesn’t.

The docs do mention this. It still took me a couple of days to internalise it.

What I actually look at in the dashboard

I dropped about half the cards from the default layout. The ones I kept:

  1. Slow Queries — by far the most useful card, because the SQL shows up next to the call site.
  2. Slow Jobs — caught a queue job that was loading every user’s relations because of a missing with().
  3. Exceptions — overlaps with Sentry, but Pulse groups them by class plus location, which is a nicer first look.
  4. Cache — hit ratios per key. Surprisingly useful. I had a key with a 4% hit rate that I’d assumed was hot.
  5. Usage by User — the one I check on bad days.

The cards I dropped: Servers, Storage, Slow Outgoing Requests (we don’t make many), and the Application Usage card. They’re fine, I just don’t use them.

The recorders I turned off (and why)

This is the section I wish I’d had on day one.

// config/pulse.php
'recorders' => [
    Recorders\SlowQueries::class => [
        'sample_rate' => 0.1,
        'threshold' => 250, // ms
        'ignore' => [
            '/(.*pulse_.*)/',     // don't record Pulse's own queries
            '/(.*telescope_.*)/', // or Telescope's
        ],
    ],

    Recorders\SlowOutgoingRequests::class => false, // disabled
],

Two changes worth flagging.

sample_rate: 0.1 for slow queries. The default is 1.0. On a busy app that’s a write-amplifier: every slow query becomes a database write. Sampling at 10% kept the resolution I needed without doubling DB writes.

threshold: 250. The default is 1000ms. That’s too lax for most web requests. I dropped it to 250 and immediately saw a WHERE ... LIKE '%foo%' query in a user search I’d forgotten about.

Also: explicitly ignore Pulse’s own tables. If you don’t, and you put Pulse on the same database as your app, the dashboard will start recording itself. Not catastrophic, but it pollutes your top-N for no reason.

Pulse vs Telescope: I run both, but for different things

I keep seeing this framed as if you have to pick one. I run both.

Telescope is the per-request inspector. When a customer reports “this page is broken”, Telescope is where I go. It records every query, every cache call, every event, every exception, for that one request.

Pulse is the across-time aggregator. When I want to know “which queries were slow last week”, Pulse is where I go.

They overlap (both can record exceptions), but the use cases are different enough that I don’t feel bad running both. Telescope is on its own database connection so it doesn’t compete with the app. Pulse uses the main connection because the writes are sampled.

If you have to pick one for a small app, pick Pulse. Telescope is a heavier tool that pays off only when something breaks. Pulse pays you back every time you glance at the dashboard.

The trap I fell into with the storage driver

Pulse has two ingest drivers: storage (buffers events and flushes them in batches to your main DB) and redis (queues events to Redis and flushes via a long-running pulse:work worker). I’d assumed I was on storage, but a stale PULSE_INGEST_DRIVER=redis in our env was sending events through a worker I hadn’t deployed.

The symptom: pgbouncer connections climbing during peak hours, and Pulse data showing up half the time.

The fix:

// config/pulse.php
'ingest' => [
    'driver' => env('PULSE_INGEST_DRIVER', 'storage'),
    'trim' => [
        'lottery' => [1, 1000],
        'keep' => '7 days',
    ],
],

What I’d do differently:

  • Set the driver explicitly. Don’t lean on env defaults; they’re easy to forget.
  • The trim config controls how long Pulse keeps data. Default is 7 days; on a small VPS I dropped it to 3.
  • Schedule the trim command, or your pulse_entries table will grow without bound.

Schedule it like this:

// app/Console/Kernel.php
$schedule->command('pulse:check')->everyMinute();
$schedule->command('pulse:trim')->daily();

Skip pulse:trim and the entries table balloons. Mine hit 4M rows in a week before I noticed. Not catastrophic, but the dashboard slowed enough that I started avoiding it. Which defeats the point.

Should you bother?

Yes if any of these match you:

  • An app slow enough that you’re guessing at causes.
  • A queue worker doing things you can’t easily inspect.
  • A team where someone other than you needs to glance at “is the app OK”.

Probably not yet if:

  • You’re still on Laravel 10 and not planning to upgrade soon. Pulse runs on 10 but a few recorders assume 11+.
  • You’re a one-person side project with 100 requests a day. Telescope is enough.
  • You already pay Datadog or New Relic for the same thing.

Setup took me about two hours including the recorder tuning and the storage-driver fix. The first useful insight (a slow query I’d been ignoring) showed up on day one. Fair trade.

If you want the longer story of the rest of the Laravel stack I lean on, I covered the new Volt syntax and how I actually use it in my Laravel Volt notes. The source on GitHub is also worth skimming if you want to see what each recorder queries; it’s all Recorder classes, no magic.

What to do this week

Open config/pulse.php. Drop your slow query threshold to 250ms. Add sample_rate: 0.1. Restart your queue workers. Wait a day. Then look at your Slow Queries card and find one you didn’t know about. There’s almost always one.

I cover this kind of “what did I actually learn from shipping it” stuff over on my project work. If you’ve disabled a Pulse recorder I haven’t, drop me a line: which one, and what burned you.