Production-ready Redis caching strategies. Covers cache-aside, write-through, read-through, cache invalidation, TTL management, and distributed locking patterns.
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.
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
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.
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.
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.
redis-cli GET user:12345
Typical output on a hit: {"name":"A.","role":"admin"}. On a miss the output is (nil).
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
EX 300 sets a time‑to‑live of 300 seconds (5 minutes). EX with PX and provide a millisecond value.OK on success; if the key already existed, the operation is still successful (the existing value is overwritten). For a true “set if not exists” that does not overwrite, use SET key value EX seconds NX.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).
(nil) — the key is not present. This is the standard Redis response for a missing key and is not an error condition.WRONGTYPE — you attempted an operation on a key holding the wrong type (e.g., calling GET on a hash). This is a logic error in the client code.ERR maxmemory … — emitted when maxmemory is hit and the eviction policy cannot free space; the write is rejected. This is the mechanism that enforces the memory cap you set earlier.When many concurrent requests encounter a missing cache key simultaneously, a stampede can overwhelm the database. Several patterns mitigate this without adding complex infrastructure.
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.
Instead of blocking callers, launch a separate process or thread that repopulates the key ahead of expiration. A typical loop:
TTL key every N seconds (e.g., 40 seconds on a 300‑second TTL).SET key value EX 300 fetch from the database.This pattern spreads the load of cache warm‑up over time rather than concentrating it at expiration.
OK from SETNX when the key was set (lock acquired).Integer 0 from SETNX when the key already existed (lock held by another client).WRONGTYPE if you mistakenly apply SETNX to a hash or list key.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.
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)
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
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.
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.
Two families of invalidation are common: time‑based (expiration) and event‑driven (explicit delete when the source of truth changes).
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
E enables key-event notifications.x enables expired events (optional).SIGHUP to the Redis process or restart for the change to take effect.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.
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).
Production‑grade Redis deployments require observability of memory, latency, and connection health.
redis-cliredis-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)
| Edge | What happens | How to detect |
|---|---|---|
| Memory‑driven eviction | When 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‑drift | Keys 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 deploy | A 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 long | If 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 mismatch | Storing 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. |
evicted_keys per minute sustains above a baseline you have established for your workload, investigate key TTLs and maxmemory sizing.instantaneous_ops_per_sec jumps by more than 2× the rolling average without a corresponding traffic increase, a cache‑miss storm or misconfigured lock is likely.blocked_clients count rises, check for long‑running MONITOR sessions or slow Lua scripts that block the main thread.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.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy