Skip to content

Database · Open source

Laravel Redis Advisor

Read-only health checks for the Redis or Valkey behind your Laravel cache, queues, sessions and Horizon, with the redis.conf line or Laravel config that fixes each problem.

Laravel 12 and 13 PHP 8.3+ v0.1.0 Free · MIT
Laravel Redis Advisor: Read-only health checks for the Redis or Valkey behind your Laravel cache, queues, sessions and Horizon, with the redis.conf line or Laravel config that fixes each problem.

What it solves

When a Redis queue shares a server with your cache under maxmemory-policy allkeys-lru, Redis can delete the list that holds your queued jobs.

No exception, no failed job, nothing in the log. php artisan cache:clear runs FLUSHDB, so a cache in the same database as your queue empties both. Memory graphs show none of this, because they do not know what your Laravel app keeps where.

The advisor reads your cache, queue, session and Horizon config to learn which Redis connection holds what, then reads INFO, a SCAN sample of keys and a few queue lengths, and reports each problem with its severity, the numbers behind it, and the fix as Redis config or a command and in Laravel terms. It never runs a fix: every command passes one guard that only allows reads.

  • Queue checks: jobs open to an allkeys-* eviction policy, backlog and the oldest job's wait, expired reservations, and Horizon trim settings and its shared default prefix
  • Key checks: queues or sessions that cache:clear would delete, colliding or default key prefixes, keys without a TTL, and big keys
  • Server checks: persistence off under queues or sessions, a failed last save, no maxmemory or a server close to it, fragmentation and swap, connections that do not answer
  • Session check: session TTLs longer than SESSION_LIFETIME, sessions that never expire, and sessions open to eviction
  • Reads the memory limit and eviction policy from INFO, so it works where CONFIG is disabled; a check that needs more is skipped with the reason
  • Artisan report with JSON output and a --fail-on exit code for CI, plus a Blade dashboard that opens only in the local environment until you define the viewRedisAdvisor gate
The Redis Advisor dashboard: summary counts for Critical, Warnings, Notices, Checks passed and Skipped, filter tabs for All, Queues, Keys, Server and Sessions, and a critical finding that queued jobs can be evicted under maxmemory-policy allkeys-lru, with the Redis fix and the Laravel fix each in its own code block.
The Redis Advisor dashboard: summary counts for Critical, Warnings, Notices, Checks passed and Skipped, filter tabs for All, Queues, Keys, Server and Sessions, and a critical finding that queued jobs can be evicted under maxmemory-policy allkeys-lru, with the Redis fix and the Laravel fix each in its own code block.

Install

Requires PHP 8.3+ and Laravel 12 and 13.

composer require amjadiqbal/laravel-redis-advisor

Usage

// app/Providers/AppServiceProvider.php: who may open /redis-advisor outside local
use Illuminate\Support\Facades\Gate;

Gate::define('viewRedisAdvisor', function ($user = null) {
    return $user?->email === '[email protected]';
});

// Then run: php artisan redis-advisor:report

Setup

The service provider registers itself and works with phpredis or predis. With no config it inspects every Redis connection the app uses: each redis cache store, each redis queue connection, the session connection when SESSION_DRIVER=redis, and Horizon's when Horizon is installed. Two connections count as one server when INFO reports the same run_id. Publish the config to change thresholds, the list of checks, or keys to ignore.

  1. 1 composer require amjadiqbal/laravel-redis-advisor
  2. 2 Optional: php artisan vendor:publish --tag=redis-advisor-config
  3. 3 Optional: REDIS_ADVISOR_CONNECTIONS=advisor_default,advisor_cache to inspect only these connections, for example ones that log in as a read-only user.
# A Redis user that can only read (the last five grants are what clients send while connecting)
ACL SETUSER advisor on >a-long-password ~* resetchannels -@all +@read +info +config|get +ping +select +hello +auth +client|setname +client|setinfo

What it sends to Redis

Every command goes through one guard, ReadOnlyGuard, which allows INFO, PING, DBSIZE, SCAN, TYPE, TTL, PTTL, EXISTS, LASTSAVE, TIME, LLEN, LINDEX, ZCARD, ZCOUNT, HLEN, SCARD, XLEN and STRLEN, plus CONFIG GET, MEMORY USAGE and STATS, and OBJECT ENCODING, FREQ and IDLETIME. Anything else throws before it reaches the server. Commands are sent raw, with no key prefix and no unserializing. The CONFIG SET, EXPIRE and UNLINK lines in some fixes are text for you to review and run yourself. Only your Redis client's own connection handshake (AUTH, SELECT, HELLO, CLIENT SETNAME) happens outside the guard.

Managed Redis and Valkey

The memory limit and eviction policy are read from INFO, and CONFIG GET is used only when INFO lacks a value, so the eviction, memory and session checks work where CONFIG is disabled. Persistence is the exception: a failed last save is read from INFO everywhere, but when AOF is off, telling whether snapshots are configured needs CONFIG GET save. Without it, the check only reports a server that has been up an hour or more with changes and no snapshot since it started; otherwise it is skipped with the reason. If MEMORY USAGE is refused, big-keys is skipped and the other key checks still run. A refused command never fails the run: a permission error comes with the ACL SETUSER grant to add, a disabled command with where to look in your provider's console. Valkey servers get the same commands and are shown as Valkey. Redis Cluster connections are not inspected: such a connection is reported by the connections check as not answering, so point the advisor at one node with its own connection.

How keys are sampled

  • keys-without-ttl, big-keys and cache-prefix share one sample per server and database, taken with SCAN in batches of scan_count (1,000), never KEYS, and stopped at sample_keys (10,000) keys.
  • Each sampled key gets PTTL and MEMORY USAGE (SAMPLES 5), pipelined one batch at a time. TYPE is only read for a big key that is reported.
  • When the sample does not cover the whole database, counts are scaled up to DBSIZE and each finding says how many keys it saw. Use redis-cli --bigkeys or --memkeys if you need every key.
  • session-ttl runs its own SCAN with MATCH on the session prefix, up to sample_keys sessions, and reads only PTTL.
  • Queue and Horizon keys are left out of the TTL and big-key checks; queue-backlog reads LLEN, ZCARD, ZCOUNT and the oldest job with LINDEX 0.
  • ignore_keys takes raw names or shell patterns that the key checks never report.

The artisan report and CI

Exit code 0 means no finding at or above --fail-on (default critical; also warning, notice or never), and 1 means at least one. Exit code 2 means nothing could be inspected: no connection answered, or the app does not use Redis at all; an invalid --fail-on value also exits 2. If only some connections fail, the report still runs, and each failed connection becomes a connections finding, critical when it holds queues, sessions or Horizon, so it counts toward --fail-on (unless --only leaves that check out). A check that throws is listed as skipped and never counts. Run the config checks in a deploy pipeline; run the usage checks on a schedule against a server with real traffic.

php artisan redis-advisor:report --only=queues            # or keys, server, sessions
php artisan redis-advisor:report --format=json
php artisan redis-advisor:report --connection=cache
php artisan redis-advisor:report --only=queue-eviction,cache-clear-flush,persistence --fail-on=critical

// routes/console.php
Schedule::command('redis-advisor:report --fail-on=warning')->hourly();

The dashboard

Open /redis-advisor for the findings worst first, each fix with a copy button, filters for queues, keys, server and sessions, the servers and connections it looked at, and the skipped checks with their reasons. It is one Blade view with inline CSS that follows the system light or dark setting, so there is nothing to build. Access goes through the viewRedisAdvisor gate. The package defines it only when your app has not, and its version allows the local environment only, so everywhere else returns 403 until you define your own. The gate runs after the configured middleware (web by default), so changing dashboard.middleware cannot remove it. Keep $user = null in your gate if guests should reach it. REDIS_ADVISOR_PATH and REDIS_ADVISOR_DOMAIN move it, and REDIS_ADVISOR_DASHBOARD=false removes the route.

Thresholds that set the severity

  • queue-eviction: critical when a cache shares the server, keys have been evicted, or memory is at 75% or more; a warning otherwise; a notice when maxmemory is 0.
  • queue-backlog: a warning at 1,000 waiting jobs or an oldest job 15 minutes old; critical at 10,000 or one hour. The wait comes from the job payload's createdAt.
  • memory-usage: a warning at 75% of maxmemory, critical at 90%. With no limit, a notice below 100 MB used and a warning above.
  • fragmentation: a warning at a ratio of 1.5 with at least 100 MB unused; critical when the ratio drops below 0.9 on 100 MB or more of data, which means swap.
  • keys-without-ttl: reported when an estimated 1,000 keys and at least 10% of non-queue keys have no TTL.
  • big-keys: a notice from 1 MB, a warning from 10 MB, at most 10 per database.
  • session-ttl: sessions outliving SESSION_LIFETIME by more than 60 seconds; a warning when that is 10% of sessions or more.
  • horizon-trimming: trims over one day, or failed jobs over 30 days, or 100,000 jobs held in one set.

Hire me

Need this built into your app, or something custom?

I wrote Laravel Redis Advisor. I can wire it into your Laravel app, extend it for your case, or build the feature around it. Tell me what you need and you'll get a plan and a quote, free.

Support

Found a bug or want a feature? Open an issue on GitHub. For help adding it to your own project, use the quote form above.

More Laravel packages

All packages
Laravel Driver.js: Build Driver.js product tours in PHP and render them with one Blade component.

Laravel Driver.js

Build Driver.js product tours in PHP and render them with one Blade component.

Laravel Sonner: Sonner toast notifications for Laravel, sent from PHP and shown without React.

Laravel Sonner

Sonner toast notifications for Laravel, sent from PHP and shown without React.

Laravel Ninja Keys: A Cmd+K / Ctrl+K command palette for Laravel, with the actions defined in PHP.

Laravel Ninja Keys

A Cmd+K / Ctrl+K command palette for Laravel, with the actions defined in PHP.

Laravel SweetAlert2: SweetAlert2 popups, toasts and confirm dialogs from Laravel controllers, Livewire and Alpine.

Laravel SweetAlert2

SweetAlert2 popups, toasts and confirm dialogs from Laravel controllers, Livewire and Alpine.

Laravel Grid.js: Turn any Eloquent query into a searchable, sortable, paginated Grid.js data table with one fluent PHP call.

Laravel Grid.js

Turn any Eloquent query into a searchable, sortable, paginated Grid.js data table with one fluent PHP call.

Laralink: Work on a Composer package inside the Laravel app that uses it, then switch back to the Packagist release.

Laralink

Work on a Composer package inside the Laravel app that uses it, then switch back to the Packagist release.

Refresher: A drop-in replacement for Laravel's RefreshDatabase that restores a cached snapshot of your test database instead of running migrate:fresh.

Refresher

A drop-in replacement for Laravel's RefreshDatabase that restores a cached snapshot of your test database instead of running migrate:fresh.

Kiln: Artisan commands and an optional signed HTTP route to clear, warm and inspect OPcache when you deploy.

Kiln

Artisan commands and an optional signed HTTP route to clear, warm and inspect OPcache when you deploy.

Laravel Media Viewer: Show private PDFs, video and audio from any Laravel disk without making the files public.

Laravel Media Viewer

Show private PDFs, video and audio from any Laravel disk without making the files public.

Laravel Swiper: Swiper sliders, carousels and image galleries for Laravel as Blade components.

Laravel Swiper

Swiper sliders, carousels and image galleries for Laravel as Blade components.

Laravel MySQL Advisor: Read-only health checks for the MySQL or MariaDB database behind your Laravel app, with the SQL and the Laravel migration to fix a problem wherever a fix applies.

Laravel MySQL Advisor

Read-only health checks for the MySQL or MariaDB database behind your Laravel app, with the SQL and the Laravel migration to fix a problem wherever a fix applies.

Laravel Webhook Console: A dashboard and replay tool for the webhooks spatie/laravel-webhook-client stores in your Laravel app.

Laravel Webhook Console

A dashboard and replay tool for the webhooks spatie/laravel-webhook-client stores in your Laravel app.

Laravel Pennant Dashboard: A browser dashboard for laravel/pennant feature flags: per-scope toggles, sticky percentage rollouts, purge and an audit log.

Laravel Pennant Dashboard

A browser dashboard for laravel/pennant feature flags: per-scope toggles, sticky percentage rollouts, purge and an audit log.

Laravel Notification Inbox: Catches every non-mail Laravel notification in local and staging instead of sending it, and shows each one as it would look on the other end.

Laravel Notification Inbox

Catches every non-mail Laravel notification in local and staging instead of sending it, and shows each one as it would look on the other end.

Laravel WebPush Kit: The browser side of Laravel web push: a subscribe button with a two-step permission prompt, a service worker, and routes that save each user's subscription. Tested in Chrome so far.

Laravel WebPush Kit

The browser side of Laravel web push: a subscribe button with a two-step permission prompt, a service worker, and routes that save each user's subscription. Tested in Chrome so far.

Need something built?

Free quote in 60 seconds