Redis Caching Patterns for Enterprise Applications

Production-ready Redis caching strategies. Covers cache-aside, write-through, read-through, cache invalidation, TTL management, and distributed locking patterns.

Orientation

Redis is an in‑memory key‑value store that is typically deployed as a caching layer in front of a persistent database. In enterprise applications it matters when read latency must stay sub‑millisecond, when session state must be shared across stateless services, or when API response aggregation can be offloaded without adding load to the backing store. This reference page covers the patterns you actually use to wire Redis into an application, the concrete commands and configuration keys you’ll type, and the failure modes that only appear after the system has been in production for a while. It is organized around what you are trying to achieve, not around a taxonomy of Redis data types.

Establish the Cache Boundary

Before writing any application code, confirm that the Redis instance is reachable and configured with appropriate resource limits. The most common deployment is a single redis-server process started with a configuration file or command‑line flags. On most Linux systems the service is managed with systemctl:

sudo systemctl start redis-server
sudo systemctl status redis-server

If you are running Redis in Docker the standard image exposes port 6379:

docker run -d --name redis-cache -p 6379:6379 redis:7-alpine

Once the process is running, verify it with the PING command:

redis-cli PING

Core configuration keys

Edit /etc/redis/redis.conf (or the path appropriate to your installation) and ensure the following keys are set for an enterprise cache:

maxmemory 512mb
maxmemory-policy allkeys-lru
hz 10
timeout 300

Apply a runtime change with CONFIG SET if you cannot restart:

redis-cli CONFIG SET maxmemory 512mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru
redis-cli CONFIG SET hz 10

maxmemory hard‑caps the memory the server will use. maxmemory-policy allkeys-lru tells Redis to evict the least recently used key when the limit is reached; the alternative volatile-ttl only evicts keys that have a TTL set, which can leave hot keys without TTLs untouched until memory pressure becomes critical. hz controls the frequency of the internal timer (in Hz); a value of 10 means the server checks for expired keys and performs other housekeeping ten times per second—a reasonable default for low‑latency workloads. timeout sets the default client timeout in seconds; clients that do not respond within this window are disconnected.

Test the boundary

redis-cli SET health:check "ok"
redis-cli GET health:check

redis-cli DEL health:check

If GET returns (nil), the cache is empty but reachable. If you receive an error such as NOAUTH Authentication required, your instance requires a password—set it with requirepass in the config file and pass --auth yourpassword to every redis-cli invocation, or export the REDISCLI_AUTH environment variable.

Read Path: Cache Miss Handling

The most common read pattern is: *check cache → miss → query database → populate cache*. Implement this flow in application code or via a client library; the underlying commands are always the same.

Minimal GET with TTL

redis-cli GET user:12345

Typical output on a hit: {"name":"A.","role":"admin"}. On a miss the output is (nil).

Atomic set‑if‑missing with expiration

When a cache miss occurs, populate the key in a single atomic step so that concurrent requests do not all hammer the database:

redis-cli SET user:12345 '{"name":"A.","role":"admin"}' EX 300

Check‑before‑set pattern (avoid unnecessary writes)

redis-cli EXISTS user:12345
# Returns integer: 1 if key exists, 0 otherwise

Use EXISTS before SET when you need to distinguish “key does not exist” from “key exists but is empty” (rare, but possible with hash fields).

Error text you’ll see

Write Path: Cache Stampede & Thundering Herd

When many concurrent requests encounter a missing cache key simultaneously, a stampede can overwhelm the database. Several patterns mitigate this without adding complex infrastructure.

SETNX‑based lock

A lightweight distributed lock can be implemented with SETNX (set if not exists). The first request that acquires the lock populates the cache; subsequent requests wait or retry.

# Acquire lock
redis-cli SETNX lock:user:12345 1 EX 10
# Check result: 1 means lock acquired, 0 means another holder

# If lock acquired, populate cache
redis-cli SET user:12345 '{"name":"A."}' EX 300

# Release lock (only if we hold it)
redis-cli DEL lock:user:12345

If the lock is not acquired, the requesting thread can either WAIT (application‑level retry loop) or serve a stale value from a background‑refresh pattern.

Background refresh

Instead of blocking callers, launch a separate process or thread that repopulates the key ahead of expiration. A typical loop:

  1. TTL key every N seconds (e.g., 40 seconds on a 300‑second TTL).
  2. If TTL falls below a threshold (e.g., 60 seconds), issue a SET key value EX 300 fetch from the database.
  3. Log errors but do not surface them to the end user.

This pattern spreads the load of cache warm‑up over time rather than concentrating it at expiration.

Error text you’ll see

Expiration & TTL Strategy

Redis itself does not have a “global default TTL.” Every key must have its expiration explicitly set, unless you rely on the volatile-ttl eviction policy, which only considers keys with a TTL. Two practical patterns dominate enterprise deployments.

Fixed TTL on write

Always attach an EX or PX when you SET a value. This is the simplest approach and ensures keys do not live forever, preventing unbounded memory growth.

redis-cli SET session:abc123 '{"user_id":42}' EX 3600
# Expires in 1 hour (3600 seconds)

Sliding TTL for frequently accessed keys

If a key is read often, its effective lifetime can be extended automatically. Redis does not provide a built‑in sliding TTL, but the pattern is straightforward in application code:

# Pseudo‑code
ttl = redis.ttl("key:")
if ttl < 60:  # less than a minute left
    redis.setex("key:", 3600, new_value)  # reset to 1 hour

Inspecting remaining TTL

redis-cli TTL user:12345
# Output: integer (seconds) or -2 if key does not exist, -1 if key exists but has no TTL
redis-cli PTTL user:12345
# Output: milliseconds, same semantics

A return value of -1 means the key will never expire unless you explicitly set one. This is a common source of memory bloat in production: keys are set without TTL and accumulate under allkeys-lru eviction, but if memory is never pressure‑tested, they persist indefinitely.

Config‑level TTL defaults

Redis has no config key that sets a default TTL for all SET operations. The only way to enforce a uniform TTL across an organization is through client‑library middleware or a proxy (e.g., using redis‑proxy or a service mesh sidecar) that rewrites SET commands to include an EX flag. If you control the client SDK, add the TTL there rather than relying on server configuration.

Invalidation Patterns

Two families of invalidation are common: time‑based (expiration) and event‑driven (explicit delete when the source of truth changes).

Event‑driven DEL

When the backing database updates a row, the application publishes an invalidation message and the cache consumer deletes the relevant key.

# Publisher (e.g., via Redis Pub/Sub or a message queue)
redis-cli PSUBSCRIBE __keyspace@0__:user:*

# Subscriber receives messages like:
# message __keyspace@0__:user:* del user:12345
redis-cli DEL user:12345

To enable keyspace notifications:

redis-cli CONFIG SET notify-keyspace-events Ex

Sharp edge: Keyspace notifications are at‑least‑once, not exactly‑once. If your application cannot tolerate duplicate DEL operations, idempotent handling (e.g., DEL is a no‑op if the key already does not exist) is required.

Pattern mismatch

A frequent gotcha: the cache stores a serialized JSON string, but the invalidation event only contains the primary key. If the consumer blindly DELs the key, but another client has already refreshed it with a new value, the next GET will miss, fetch from the database, and overwrite the new value—effectively a no‑op, but it adds unnecessary database load. Always check the current value or version before invalidating, or use a cache‑generation identifier (e.g., store v3 alongside the payload and compare before DEL).

Monitoring & Sharp Edges

Production‑grade Redis deployments require observability of memory, latency, and connection health.

Core metrics via redis-cli

redis-cli INFO memory
# Fields: used_memory, used_memory_human, maxmemory, keys, evicted_keys, expired_keys,
#         instantaneous_ops_per_sec, blocked_clients

redis-cli INFO keyspace
# Lists all keys and their TTLs; useful for sanity‑checking before a deploy

redis-cli CLIENT LIST
# Shows connected clients, their IDs, addresses, and flags (e.g., slave, pubsub)

Common sharp edges

EdgeWhat happensHow to detect
Memory‑driven evictionWhen maxmemory is hit and allkeys-lru is active, Redis evicts the least recently used key. If a critical key is evicted, subsequent reads become cache‑misses and hit the database.Monitor evicted_keys from INFO memory. A sudden spike correlates with cache‑miss storms.
TTL‑driftKeys set without EX/ PX never expire. Under volatile-ttl policy they are never evicted either, leading to gradual memory bloat.Run redis-cli INFO keyspace periodically; look for keys with TTL -1.
Cache‑miss storm on deployA rolling deploy that replaces the cache instance or clears a subset of keys causes all subsequent reads to miss, spiking database load.Coordinate cache warm‑up: pre‑populate hot keys via a script before taking the old instance out of rotation.
SETNX lock timeout too longIf a process holds a SETNX lock for longer than the business‑logic expects (e.g., 10 seconds) and crashes without DEL, other requests remain blocked until the lock’s TTL expires.Set lock TTL conservatively (e.g., 2‑5× the expected processing time) and use a watchdog script that DELs the lock if the holder exceeds a safe deadline.
Serialization version mismatchStoring JSON v2 of an object, then a new deploy changes the schema to v3. Old cached values fail to deserialize or produce wrong fields.Include a version field in the cached payload and compare it on read; if it mismatches, fetch from the database and update the cache.
Network partition in Redis Cluster (if applicable)Hash slots become inconsistent; some clients can read a key while others cannot.If using Redis Cluster, monitor cluster slots and cluster meetings via redis-cli CLUSTER INFO. For single‑node deployments, a network partition simply disconnects clients; the server remains available if it has a quorum (Redis Sentinel) or is standalone.

Alert‑worthy thresholds (prose, not stats)

Wrap‑up

These patterns cover the day‑to‑day operations most enterprise Redis deployments require: sizing and protecting memory, handling reads and writes without stampedes, setting and managing TTLs, invalidating stale entries, and observing the quiet failures that only surface under load. Keep the configuration keys (maxmemory, maxmemory-policy, hz, timeout) under version control, test SETNX lock timeouts in a staging environment, and always inspect TTL and INFO keyspace after a deploy. The commands shown here—redis-cli SET, GET, EXISTS, SETNX, EXPIRE, TTL, DEL, CONFIG SET—are the exact building blocks; adapt the surrounding application logic to your language and architecture, but the underlying protocol remains the same.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.