Skip to content
Cache, sessions, queues, rate limiting, locks, pub/sub — the Redis patterns I use in production Laravel apps, with the t...

Six Redis Patterns Every Laravel App Eventually Needs

Al Amin Ahamed

Al Amin Ahamed

Senior Software Engineer

· Updated 5 hours ago 10 min read

Redis is the Swiss army knife of Laravel infrastructure. The same Redis instance can do session storage, cache, queue, rate limiting, locks, and pub/sub. Each pattern has a sweet spot — and a way to break things if used wrong.

Cache

The most common use, also the most misused.

PHP
// Tag invalidation
Cache::tags(['posts', 'sidebar'])->put('top_posts', $posts, 3600);
Cache::tags('posts')->flush(); // invalidates anything tagged 'posts'

// Atomic remember
$posts = Cache::remember('top_posts', 3600, fn () => Post::featured()->get());

The remember pattern looks atomic but isn't. Two concurrent requests can both miss the cache and both run the closure. For expensive closures, use flexible (added in Laravel 11):

PHP
Cache::flexible('top_posts', [3600, 7200], fn () => Post::featured()->get());

flexible accepts a stale window: serve cached for 1 hour, return stale value up to 2 hours while regenerating in the background. This is the Stale-While-Revalidate (SWR) pattern. Brilliant for traffic-heavy pages.

Sessions

ENV
SESSION_DRIVER=redis

Redis sessions are faster than database sessions and survive across multiple web servers. They expire automatically — no cleanup cron.

Gotcha: Redis is in-memory. A reboot loses all sessions, logging users out. Set Redis to persist (appendonly yes) for production.

Queues

ENV
QUEUE_CONNECTION=redis

The default Redis driver is fine for ~10k jobs/minute. Above that:

  • Use multiple Redis databases for different queue names (avoids hot keys)
  • Use Redis Cluster if your queue key count exceeds ~100k
  • Consider Horizon for monitoring; the dashboard pays for itself

The most common bug: queue workers and the web app sharing the same Redis instance for cache and queue. Cache writes can block queue reads if the cache is busy. Solution: separate Redis instances or at minimum separate database numbers.

ENV
REDIS_DB=0           # cache
REDIS_QUEUE_DB=1     # queue
REDIS_CACHE_DB=0

Rate Limiting

Laravel's RateLimiter facade uses Redis under the hood:

PHP
RateLimiter::attempt(
    'login:' . $request->ip(),
    $perMinute = 5,
    fn () => $this->doLogin($request),
    $decaySeconds = 60,
);

Per-IP login throttling: 5 attempts per minute, then locked out.

For routes:

PHP
Route::middleware('throttle:60,1')->group(/* ... */);

60 requests per minute per authenticated user (or per IP if guest).

Atomic Counters

For real-time stats:

PHP
Redis::incr('post:42:views');                            // atomic increment
Redis::expire('post:42:views', 86400);                   // 24h TTL
$views = (int) Redis::get('post:42:views');

Async-flush to the database with a daily cron:

PHP
Schedule::command('stats:flush')->daily();

Critical: Redis is not durable for counters. A crash loses uncommitted increments. For exact billing counters, use the database. For approximate analytics counters, Redis is fine.

Locks

Distributed locks prevent two workers from processing the same job:

PHP
Cache::lock('process-order:' . $orderId, 60)->block(5, function () {
    // Critical section. Times out after 5 seconds of waiting.
});

The block parameter waits up to N seconds for the lock. The 60 is the lock's max hold time — if the holder dies, the lock auto-releases after 60 seconds.

Without distributed locks, race conditions are real. With them, you're trading correctness for occasional timeouts when contention is high.

Pub/Sub for Real-Time

For broadcasting events to WebSocket clients:

PHP
Redis::publish('order.created', json_encode($order));

// In a worker
Redis::subscribe(['order.created'], function (string $message) {
    // dispatch to ws clients
});

Laravel Echo's Redis broadcaster uses this internally. For most apps, just configure broadcasting in config/broadcasting.php and let Laravel handle the pub/sub.

What I Don't Use Redis For

  • Long-term storage — even with persistence, Redis is RAM-bound. Anything you need to keep, put in Postgres or S3.
  • Complex queries — Redis is key-value at heart. The fancy data structures (sorted sets, hashes) work, but for any structured query I'd rather use Postgres.
  • Transaction-heavy workloads — Redis transactions exist (MULTI/EXEC) but lack the isolation guarantees of Postgres. If you need ACID, Postgres.

Memory Tuning

Redis on a 1GB VPS will OOM-crash if you don't set maxmemory:

maxmemory 800mb
maxmemory-policy allkeys-lru

allkeys-lru evicts least-recently-used keys when full. For a cache use case, this is what you want. For a session store you can't lose, use noeviction and either size up or split workloads across multiple instances.

Monitoring

BASH
redis-cli --latency        # measure ms latency from CLI to Redis
redis-cli info             # memory, hit rate, connected clients
redis-cli monitor          # all commands in real-time (don't run on prod)

Hit rate (keyspace_hits / (keyspace_hits + keyspace_misses)) under 80% means your TTLs are too short or your cache is too small.

What I'd Tell My Past Self

  • Use the same Redis instance for everything; separate via DB numbers if needed
  • Set maxmemory immediately, don't wait for the OOM
  • Turn on persistence (appendonly yes) for sessions; can skip for pure cache
  • Sample, don't trace — Redis monitoring tools that try to capture every command will crash a busy production instance
Share 𝕏 in
Al Amin Ahamed

Al Amin Ahamed

Senior software engineer & AI practitioner. 5+ years shipping Laravel platforms, WordPress plugins, WooCommerce extensions, and AI-driven products.

About me →

More from the blog

Need this kind of work shipped?

Available for freelance and consulting.

Laravel platforms, WordPress plugins, WooCommerce extensions, and AI integrations.