Redis Caching Patterns for Production: Cache-Aside, Write-Through, TTL, and Cluster Setup

Published 15 min read
rediscachingcache-asidewrite-throughttlclustersentinel

Redis reduces database load by 60-90% when implemented with the right caching pattern for your access profile. Cache-aside works for read-heavy workloads, write-through ensures consistency, and write-behind optimizes write performance. This guide covers each pattern with production configurations, TTL strategies, eviction policies, and high-availability architectures.

The 2025 Redis Labs benchmark report shows that properly configured Redis caching reduces P99 API latency from 250ms to under 5ms for cached responses, while cutting database query volume by an average of 78%. The most common production mistake is choosing the wrong caching pattern, which leads to stale data, cache stampedes, or unnecessary complexity.

---

Cache-Aside Pattern (Lazy Loading)

Cache-aside is the most common pattern. The application checks Redis first, falls back to the database on miss, and populates the cache after reading from the database.

import redis

import json

import asyncpg

redis_client = redis.Redis(host="localhost", port=6379, db=0)

async def get_user(user_id: int):

# Step 1: Check cache

cache_key = f"user:{user_id}"

cached = redis_client.get(cache_key)

if cached:

return json.loads(cached)

# Step 2: Cache miss - query database

conn = await asyncpg.connect(dsn="postgresql://localhost/myapp")

row = await conn.fetchrow(

"SELECT id, email, name FROM users WHERE id = $1", user_id

)

await conn.close()

if row is None:

return None

# Step 3: Populate cache with TTL

user_data = dict(row)

redis_client.setex(cache_key, 3600, json.dumps(user_data))

return user_data

AspectCache-Aside Behavior

Read pathCache first, then DB on miss
Write pathWrite to DB, invalidate cache
ConsistencyEventually consistent (TTL-bounded)
Cache populationOn-demand (lazy)
Best forRead-heavy workloads (80%+ reads)
RiskCache stampede on cold start

---

Write-Through Pattern

Write-through writes to both the cache and database simultaneously. The cache is always up to date, eliminating stale reads at the cost of higher write latency.

async def update_user(user_id: int, name: str, email: str):

# Step 1: Write to database

conn = await asyncpg.connect(dsn="postgresql://localhost/myapp")

await conn.execute(

"UPDATE users SET name = $1, email = $2 WHERE id = $3",

name, email, user_id

)

await conn.close()

# Step 2: Update cache immediately

cache_key = f"user:{user_id}"

user_data = {"id": user_id, "name": name, "email": email}

redis_client.setex(cache_key, 3600, json.dumps(user_data))

return user_data

---

Write-Behind Pattern (Write-Back)

Write-behind writes to the cache first and asynchronously flushes to the database. This provides the lowest write latency but risks data loss if Redis crashes before flushing.

import time

import threading

WRITE_BUFFER_KEY = "write_buffer:users"

def buffer_user_update(user_id: int, data: dict):

"""Queue write in Redis, flush to DB asynchronously."""

redis_client.hset(f"user:{user_id}", mapping=data)

redis_client.sadd(WRITE_BUFFER_KEY, user_id)

def flush_write_buffer():

"""Background worker flushes buffered writes to DB."""

while True:

pending = redis_client.smembers(WRITE_BUFFER_KEY)

for user_id in pending:

user_id = int(user_id)

data = redis_client.hgetall(f"user:{user_id}")

# Write to database

sync_to_database(user_id, data)

redis_client.srem(WRITE_BUFFER_KEY, user_id)

time.sleep(5) # Flush every 5 seconds

<h1>Start background flusher</h1>

threading.Thread(target=flush_write_buffer, daemon=True).start()

PatternWrite LatencyRead ConsistencyData Loss RiskComplexity

Cache-asideDB latencyEventually consistentNoneLow
Write-throughDB + cache latencyStrongly consistentNoneMedium
Write-behindCache latency onlyStrongly consistentIf Redis crashesHigh

---

TTL Strategies

TTL (time-to-live) determines how long cached data remains valid. The right TTL balances freshness against cache hit rate.

<h1>Redis TTL commands</h1>

<h1>Set key with 1-hour TTL</h1>

SET user:123 '{"name":"Alice"}' EX 3600

<h1>Set TTL on existing key</h1>

EXPIRE user:123 3600

<h1>Check remaining TTL</h1>

TTL user:123

<h1>Remove TTL (persist forever)</h1>

PERSIST user:123

Data TypeRecommended TTLReasoning

User profile1 hourChanges infrequently, stale data is low risk
Session data24 hoursMatches session expiration
Product catalog15 minutesPrices and availability change moderately
API rate limits60 secondsMust be accurate for enforcement
Search results5 minutesBalances freshness with performance
Configuration30 secondsCritical settings need fast propagation

---

Eviction Policies

When Redis reaches its memory limit, the eviction policy determines which keys are removed.

<h1>Set max memory and eviction policy in redis.conf</h1>

maxmemory 2gb

maxmemory-policy allkeys-lru

<h1>Or set at runtime</h1>

redis-cli CONFIG SET maxmemory 2gb

redis-cli CONFIG SET maxmemory-policy allkeys-lru

PolicyBehaviorBest For

noevictionReturns error when fullCritical data, no cache
allkeys-lruEvicts least recently usedGeneral purpose caching
allkeys-lfuEvicts least frequently usedSkewed access patterns
volatile-lruLRU among keys with TTLMixed persistent + cache
volatile-ttlEvicts shortest TTL firstTime-sensitive data
allkeys-randomRandom evictionUniform access patterns

For most production workloads, `allkeys-lru` is the right default. Switch to `allkeys-lfu` if your access pattern is heavily skewed (10% of keys serve 90% of requests).

---

Redis Cluster vs Sentinel

Redis Sentinel (High Availability)

Sentinel provides automatic failover for a single-master Redis setup. Use Sentinel when your dataset fits in a single Redis instance (under 25 GB) and you need automatic failover.

<h1>sentinel.conf</h1>

sentinel monitor mymaster 10.0.0.1 6379 2

sentinel down-after-milliseconds mymaster 5000

sentinel failover-timeout mymaster 60000

sentinel parallel-syncs mymaster 1

Redis Cluster (Horizontal Scaling)

Redis Cluster distributes data across multiple masters using hash slots. Use Cluster when your dataset exceeds what a single instance can hold or when you need write throughput beyond a single node.

<h1>Create a 6-node cluster (3 masters, 3 replicas)</h1>

redis-cli --cluster create \

10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \

10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \

--cluster-replicas 1

<h1>Check cluster health</h1>

redis-cli --cluster check 10.0.0.1:6379

FeatureSentinelCluster

Data distributionSingle masterSharded across masters
Max dataset sizeSingle node limitSum of all masters
Write throughputSingle masterDistributed writes
FailoverAutomatic (10-30s)Automatic (10-30s)
Multi-key operationsFull supportSame hash slot only
Operational complexityLowMedium-High

For detailed database caching architecture, see our database connection pooling guide. If you are evaluating whether Redis is the right caching layer for your stack, our MongoDB vs PostgreSQL comparison covers when built-in database caching is sufficient. Teams running Kubernetes can automate Redis deployment using the patterns in our Kubernetes autoscaling guide.

---

Frequently Asked Questions

How much memory should I allocate to Redis?

Allocate no more than 75% of available system memory to Redis. Redis needs overhead for background saves (fork + copy-on-write), client buffers, and internal data structures. A server with 8 GB RAM should set maxmemory to 6 GB. Monitor memory usage with `redis-cli INFO memory` and set alerts at 80% of maxmemory.

How do I prevent cache stampede?

Cache stampede occurs when a popular key expires and hundreds of concurrent requests hit the database simultaneously. Three solutions: (1) Use a lock (SETNX) so only one request rebuilds the cache while others wait. (2) Refresh popular keys before expiration using a background worker. (3) Use probabilistic early expiration where each request has a small chance of refreshing the key before TTL.

Should I use Redis or Memcached?

Redis in almost all cases. Redis supports data structures (lists, sets, sorted sets, hashes), persistence, replication, Lua scripting, and pub/sub. Memcached only advantage is slightly lower memory overhead per key for simple string caching. The operational simplicity of running one caching system (Redis) outweighs the marginal memory savings of Memcached.

Can Redis be used as a primary database?

Redis can persist data to disk (RDB snapshots + AOF logs), but it is not designed as a primary database. It lacks SQL, complex queries, and referential integrity. Use Redis for caching, session storage, rate limiting, queues, and real-time leaderboards. Keep your primary data in PostgreSQL or MongoDB.

How do I monitor Redis in production?

Run `redis-cli INFO` for real-time metrics. Key metrics to monitor: used_memory vs maxmemory, keyspace_hits vs keyspace_misses (cache hit rate), connected_clients, instantaneous_ops_per_sec, and rdb_last_bgsave_status. Export these to Prometheus with redis_exporter and create Grafana dashboards with alerts at 90% memory usage and cache hit rate below 80%.