In Laravel Pulse, what changes when you set PULSE_INGEST_DRIVER=redis, and what must you run and trim to keep it healthy?
answer
- default ingest writes after the response
- Redis stream laravel:pulse:ingest
- pulse:work digests into database tables
- trim lottery 1 in 1,000
- storage keep '7 days', capped
basics
~20 sWith PULSE_INGEST_DRIVER=redis, requests push Pulse entries onto a Redis stream instead of writing to the database; a long-running php artisan pulse:work moves them into Pulse's tables, trims old data, and must be restarted with pulse:restart on deploy.
solid answer
~50 sBy default (`PULSE_INGEST_DRIVER=storage`) Pulse writes its buffered entries straight to the database once the response has been sent, after each processed job, or when more than `PULSE_INGEST_BUFFER` (5,000) entries pile up. The `redis` driver instead pipelines an `XADD` per entry onto the `laravel:pulse:ingest` stream, which is much cheaper for the request worker. Something must then drain it: `php artisan pulse:work` loops every second, reads the stream in chunks of 1,000, stores them and deletes them, and trims database storage every ten minutes. Keep it alive with a process monitor and run `php artisan pulse:restart` on deploy; in Laravel 13 Pulse also registers that command with the framework's reload hook. Trimming is a 1-in-1,000 lottery per ingest: with Redis it trims the stream by `PULSE_INGEST_KEEP`, while database rows follow `PULSE_STORAGE_KEEP` (default `7 days`), which can shorten retention but never extend it past seven days. Use Redis 6.2+ and a connection separate from the queue.
code
ini · 4 linesPULSE_INGEST_DRIVER=redis
PULSE_REDIS_CONNECTION=pulse
PULSE_INGEST_KEEP="7 days"
PULSE_STORAGE_KEEP="3 days"go deeper
Recall that PULSE_INGEST_DRIVER=redis needs php artisan pulse:work running, and pulse:restart on each deploy.
Explain the default storage ingest after the response, the Redis stream plus pulse:work digest, and the two keep settings with their 7-day defaults.
Operate it: process monitoring, a shared cache for restart signals, a Redis connection separate from the queue, and what the dashboard shows when the worker is down.
Judge when Pulse's write load justifies Redis and a worker, versus a dedicated database or reduced recorders, and where long-term metrics should live instead.
## Where Pulse normally writes **Laravel Pulse** buffers entries in memory while a request or job runs. With the default **storage ingest** (`pulse.ingest.driver` = `storage`), the buffer is flushed straight into the database: - when the HTTP request lifecycle ends (Pulse hooks the kernel's terminate phase, so the response has already been sent); - when a queue worker loops or stops, so entries from each job are written; - mid-request, if more than `PULSE_INGEST_BUFFER` entries (5,000 by default) accumulate. Those writes are upserts into `pulse_entries`, `pulse_aggregates` and `pulse_values` inside a transaction. The client does not wait for them, but the PHP worker that served the request stays busy until they finish, and on a busy app those writes land on your database. ## What the Redis ingest changes Setting `PULSE_INGEST_DRIVER=redis` binds `RedisIngest` instead: 1. At flush time, each entry is serialized and appended with `XADD` to the Redis stream `laravel:pulse:ingest`, all in one pipeline. 2. The request or job is done with Pulse as soon as that pipeline returns. 3. A separate process, `php artisan pulse:work`, performs the database writes. The docs list requirements: Redis 6.2 or newer, `phpredis` or `predis` as the client, and a Redis connection for Pulse (`PULSE_REDIS_CONNECTION`) that is **different from the one your Redis queue uses**. ## What pulse:work does `pulse:work` is a long-lived loop: - every second it calls `digest()`: `XRANGE` up to 1,000 entries (`pulse.ingest.redis.chunk`), store them in the database, `XDEL` them, and repeat until a short chunk shows the stream is drained; - every ten minutes it trims database storage; - it compares a cache key (`laravel:pulse:restart`) with the value it saw at start-up and exits when `pulse:restart` changes it; - `--stop-when-empty` makes it exit after one pass. Because it is long-lived it keeps old code in memory, so the deploy runs `php artisan pulse:restart` and a process monitor such as Supervisor starts it again. The restart signal lives in the cache (`PULSE_CACHE_DRIVER`, or the default store), so that store must be shared by every server. In Laravel 13, Pulse's service provider also registers `pulse:restart` with the framework's provider `reloads()` hook. ## Trimming: two stores, two keep settings | What is trimmed | Setting | Default | Mechanism | |---|---|---|---| | Redis stream | `pulse.ingest.trim.keep` (`PULSE_INGEST_KEEP`) | `7 days` | `XTRIM` by `MINID` for a duration, or `MAXLEN` for an integer | | Database tables | `pulse.storage.trim.keep` (`PULSE_STORAGE_KEEP`) | `7 days` | `DELETE` of rows older than the cutoff | The trigger is `pulse.ingest.trim.lottery`, `[1, 1_000]`: each time entries are ingested there is a 1-in-1,000 chance of calling the ingest's `trim()`. With the storage ingest that trims the database; with the Redis ingest it trims the stream, and `pulse:work` handles the database every ten minutes. Database storage is clamped: if `keep` is longer than seven days, Pulse still deletes data older than seven days, because the dashboard never shows more. Aggregates are also trimmed per period, so hourly buckets vanish sooner than weekly ones. ## Failure modes to watch - **pulse:work not running**: the stream grows, the dashboard freezes at the last digest, and Redis memory climbs until the stream trim catches up. - **Shared Redis with the queue**: the docs forbid it; keep Pulse's stream on its own connection. - **No shared cache**: `pulse:restart` cannot reach processes on other servers, which keep running old code. - **Pulse errors**: capture failures are swallowed so the app keeps working; register `Pulse::handleExceptionsUsing()` to log them. ## Choosing between the two ingest drivers - Stay on **storage** when traffic is modest: there is no extra process, and Pulse's writes already happen after the response is sent. - Move to **redis** when those writes visibly occupy PHP workers or load the database at peak, and you already run Redis and a process monitor. - Consider a dedicated `PULSE_DB_CONNECTION` first if the concern is contention on the application database rather than worker time. - Setting the ingest driver to `null` binds a no-op ingest that discards entries; `PULSE_ENABLED=false` is the clearer way to switch recording off.
- What happens to the dashboard if pulse:work dies while the Redis ingest is on?Requests keep appending to the `laravel:pulse:ingest` stream, but nothing moves entries into the database, so every card stops updating at the last digest. The stream keeps growing until the 1-in-1,000 trim lottery cuts it back by `PULSE_INGEST_KEEP`. Restarting `pulse:work` drains the backlog in chunks of 1,000.
- Can you keep 30 days of Pulse data by setting PULSE_STORAGE_KEEP="30 days"?No. `DatabaseStorage::trim()` clamps the cutoff so nothing older than seven days survives, whatever `keep` says; a shorter value such as `3 days` does take effect. Pulse is a rolling operational view, so long-term history belongs in a separate metrics system.
saying these in an interview costs you the question
- The Redis ingest writes to Pulse's tables itself, so no extra process is needed.
- Pulse's Redis stream can share the Redis connection the queue uses.
- PULSE_STORAGE_KEEP="30 days" gives a 30-day dashboard.
- With the default storage ingest, the client waits for Pulse's database writes.
- pulse:work picks up new code on its own after a deploy.