Skip to content

Data Engineering

Redis Caching, Queues and Real-Time Features in Laravel at Scale

How to split cache, Horizon queues and pub/sub in Laravel so a FLUSHALL or eviction policy cannot take down jobs, sessions and tills together.

Prakash Tank

Prakash Tank

October 28, 2026 · 11 min read

Redis is the Swiss Army knife sitting next to many Laravel apps: cache, queues, rate limits, sessions, locks, and the pub/sub behind websockets. Used with a plan, it makes a product feel fast. Used as a junk drawer, it becomes a single point of outage with no persistence story and a key namespace nobody can explain. This guide is how we would put Redis under a Laravel app that has left the “one server, CACHE_DRIVER=file” years behind.

DayDecoder builds and operates Laravel products from Ahmedabad for teams in the US, UK, Europe and Australia. This is engineering practice, not a benchmark contest with invented numbers.

Key takeaways

  • Separate Redis by job when you can: cache eviction should not flush a queue, and a runaway cache should not block Horizon.
  • Every key needs a prefix, a TTL, and an owner. “Forever” keys are memory leaks with extra steps.
  • Queues (Horizon) and locks are for correctness; cache is for speed. Do not mix the success criteria.
  • Real-time (Reverb, Soketi, or similar) needs a pub/sub story and a plan when Redis is down: the rest of the app should still take orders.
  • Measure hit rate, memory, eviction, and queue wait. Tune from that, not from a blog’s maxmemory anecdote.

The jobs Redis actually does in Laravel

Job Laravel piece If Redis is down
HTTP cache / application cache Cache facade, Redis store App should still work, slower; stampedes are the risk on recovery
Queues Horizon, Redis queue driver Jobs stop; user-facing writes should not silently vanish — design the UX
Locks Cache::lock, unique jobs Double processing possible; decide fail-closed vs fail-open
Rate limits RateLimiter You may fail open (abuse) or closed (lock out everyone) — pick on purpose
Sessions Redis session driver Users signed out or errors; sticky file/DB sessions as fallback is rare but possible
Real-time Reverb / broadcast driver Live UI stalls; HTTP API should remain the source of truth

Those different failure modes are why “one Redis, one password, one FLUSHALL” is a story we still hear. Prefer two or three logical instances (or at least Redis logical DBs only if you understand they are not a security boundary): cache vs queue vs pub/sub. Managed Redis (ElastiCache, Memorystore, Azure Cache, and similar) with persistence on the queue instance is a typical production shape. Persistence on a pure cache is optional; persistence on a queue is not.

Caching without lying to the customer

Cache derived data, not the only copy of an order. The database remains the system of record. Key by tenant and by version of the query. Invalidate on write, or set short TTLs if you cannot. Cache stampede: use locks or “early refresh” for expensive keys. Do not cache per-user pages that contain other people’s data; that is a security bug, not a performance win.

Remember Cache::remember can store null if you are not careful. Remember serialization of Eloquent models is a trap; cache arrays or DTOs.

For HTML or HTTP caching, CDNs and Cache-Control may be the better first lever. Redis is not a substitute for an index on tenant_id, created_at.

Queues: Horizon is an operations product

Workers, retries, backoff, and a failed_jobs table you actually look at. Unique jobs and idempotency for anything that charges a card or sends a parcel. Redis here is a transport. If a job is not safe to run twice, the job must be safe to run twice anyway, because transports duplicate under failure. That is the same lesson as Kafka consumers, at smaller scale.

Fairness: a huge tenant should not starve others. Separate queues by priority and tenant class if you have to. Timeout jobs that call LLMs; AI work does not belong on the default timeout=60 web mental model — see AI in Laravel.

Horizon’s dashboard is not a pager. Export metrics (pending, throughput, failed) to whatever you already page on.

Locks and uniqueness

Use locks for “only one inventory decrement at a time”, with a TTL longer than the critical section and shorter than a human lunch. Lost locks (process died) should not hold stock forever. Unique jobs for “send this invoice email once”. Document what happens if the lock cannot be acquired: wait, or return 409, or enqueue later.

Real-time features

Presence, live dashboards, chat-ish notifications: broadcast from the backend after the write commits. The websocket server is a projector of facts, not the ledger. If Redis pub/sub drops, clients reconnect and fetch state via HTTP. Do not invent a second source of truth in memory on the Node sidecar.

Authorise channels the same as HTTP policies. A channel named orders without a tenant id is a leak.

Memory, eviction, and “it was fast yesterday”

Set maxmemory and an eviction policy on cache instances (often allkeys-lru or volatile-lru depending on whether keys have TTLs). Do not use noeviction on a cache unless you like 500s. Queues should not share that policy. Monitor evicted_keys, used_memory, fragmentation, and blocked clients.

Big keys (a 40 MB JSON blob of “all products”) will hurt everyone. Split or do not store them. Pipelines for bulk, not N round trips in a loop in a request.

TLS in transit in the cloud. A password. No Redis on 0.0.0.0 to the world. This still happens.

A scale path that is honest

  1. One managed Redis for cache + a second for queues when you outgrow mixing them.
  2. Read replicas only if your driver and job mix actually read.
  3. Cluster when a single shard’s memory or CPU is the limit, not because “cluster” sounds like scale. Cluster changes key-slot rules; tests must run in cluster mode before you find out in production that a multi-key op is illegal.
  4. When the bottleneck is MySQL, fix queries. Redis will not save an N+1.

DayDecoder: 10+ years, 103 customers. We would rather add an index than a new Redis cluster. Email contact@daydecoder.com.

Key design, prefixes and TTL policy

Pick a prefix per bounded context: ord:, inv:, sess:, plus tenant. Include a schema version in the key when the value shape changes, so you do not deserialize yesterday. Default TTL for cache keys unless you have a written reason. A weekly job that lists keys without TTL is a cheap audit. If the list is long, you have a leak.

Avoid KEYS * in production. Use SCAN in a maintenance window if you must. The fact that a tutorial used KEYS is not a design.

When you invalidate, prefer version bump (change a prefix) over hunting individual keys if a whole store’s catalog changed. Hunting is how you miss one and serve a stale price.

Horizon at more than one app server

Multiple Horizon supervisors on multiple machines need a story for the same Redis: one prefix, one metrics identity, and workers that can die without leaving reserved jobs forever. Timeouts and retry_after must exceed the longest job. LLM jobs get their own queue and a long timeout, still with a ceiling. If a job can run thirty minutes, it should probably be a chunked pipeline, not a single reservation.

Failed jobs need a budgeted human. A table that grows forever is not “at scale”. It is a landfill. Alert on failed rate, not only on pending.

When Redis is the outage

Write the playbook: cache instance down (app continues, maybe disable the heaviest pages); queue instance down (writes that enqueue must tell the user “we will retry”, or write to a DB outbox); both down (this is a platform incident). Practice failovers on staging. Managed Redis failover is not always instant; your app should survive a short blip without stampedes. Lock around rebuilds of expensive keys.

Do not FLUSHALL on a box that also holds queues. Split them so a panicked cache clear cannot destroy jobs. We have seen that once more than zero times, which is too many.

Laravel-specific footguns

Serializing models into cache, once() helpers that surprise you in octane/long workers, config cached with an old Redis host, the pure-PHP Redis client versus the phpredis extension (they disagree on pipelines and clusters), and unique job keys that hash too much of the payload so they never collide when they should. Octane/Swoole workers will reuse Redis connections; understand that before you blame the network. If you are on serverless PHP, Redis might be the wrong session store; use what the platform documents.

We would rather read your config/database.php and Horizon config on a call than guess. Bring INFO and a slow request trace.

Laravel feeling slow, or Horizon in the dark? We tune cache, queues, and the database together. Web application development, Laravel, or contact.

Multi-tenant cache poisoning and the tests we write

A cache key without tenant id is a data leak. We write a test that stores a value as tenant A and asserts tenant B does not see it, including on shared HTTP caches you might add later. The same for broadcast channel names. If you use tags, know that not every Redis driver implements flush tags the way you hope. Prefer explicit keys you can reason about.

When you must cache a permissioned query, include a version of the ACL in the key or do not cache. Stale permission is worse than a slow query. Security beats a millisecond. This is the POS and logistics tenancy story again, because it is always the same bug. Write the test once in a shared trait if you must, but write it.

Capacity planning: look at used_memory weekly, not only after an incident. Growth of a single key pattern (a per-user blob that should have been a column) is easier to stop early. If you need numbers, bring INFO and SCAN samples to the call; we will not invent a “typical Laravel Redis size” on this page because there is not one. A 200-user SaaS and a POS estate are different animals that happen to share a client library.

Handover checklist

You should leave with: which Redis instances exist (cache vs queue vs pub/sub), prefixes, TTL policy, Horizon timeouts, what happens when each instance dies, and a forbidden-commands list (FLUSHALL on shared boxes). Include a tenant cache test. Include how to read INFO and who gets the memory alert. If the only person who understands Horizon left, the checklist is how the next person starts without flushing production to “see if it helps”. It will not help. It will delete jobs. Write that sentence in the runbook in the same tone you would use for “do not drop the database”. Then practice a cache-only failover on staging so the first time is not a customer incident. Note how long the stampede lasts and which keys you lock; that measurement is the runbook’s most useful paragraph.

FAQ

Should sessions live in Redis?

It is convenient for multiple app nodes. It also makes Redis outage a logout event. Many apps are fine with it if Redis is managed and monitored. Some keep sessions in the database on purpose. Pick, do not inherit a tutorial.

Is Redis a database for orders?

No. It can hold a cache of an order. Durability and query tools for money belong in Postgres or MySQL, with backups you have restored in a drill.

Redis vs Kafka for jobs?

Jobs with retries and a few consumers: Redis/Horizon or SQS. Replayable facts for many independent systems: Kafka. See the Kafka article in this series. Using Redis as a fake Kafka usually ends in a rewrite.

How do we cache AI responses?

Key by tenant, prompt version, model, and a hash of inputs. TTL by how stale the answer may be. Do not cache another tenant’s completion. Token spend is a reason to cache; privacy is a reason to be careful.

Can you migrate us off a single DigitalOcean Redis droplet?

Yes. Typical work is split instances, persistence on queues, TLS, and dashboards. Indicative effort is a scoped ops plus app change, not a rewrite, when the key layout is sane. Quote after we see INFO and the key prefix list.

Prakash Tank

Written by

Prakash Tank

Founder & Full-Stack Architect, DayDecoder

Prakash started as a certified Laravel developer and grew into a full-stack architect who owns a project end to end: requirements, architecture, development, deployment, and support. He designs event-driven systems with...

More about Prakash

Keep reading

Related articles

Let's talk

Turn this into a plan for your project.

Share what you are building. We will reply within one business day with honest advice and next steps.

  1. 1

    We reply within 24 hours

    A real person reads your message and replies on the next business day at the latest.

  2. 2

    Free 30-minute call

    We talk through your goals, users, and budget, and answer every question.

  3. 3

    Proposal & fixed quote

    You get a written scope, timeline, and price. No obligation.

contact@daydecoder.com Ahmedabad, India

Get a free consultation

Takes under a minute. No spam, no obligation.

What do you need help with?

Your details stay private. NDA on request.