Skip to content
Database

Redis in Production: The Config That Prevents the Pager

Organized by the incident it prevents: maxmemory and eviction policies, AOF vs RDB persistence blends, Sentinel vs Cluster reality, the fork-memory trap and the five-number dashboard.

5 min read Updated Sep 2, 2026
Redis in Production: The Config That Prevents the Pager

Redis has a cruel property: the default configuration works so well in development that most teams never read the config file until the day it matters — which is the day Redis ate all the RAM, or restarted empty, or split-brained during a failover. I've been paged for all three. Here's the configuration walkthrough that prevents them, organized by the incident it prevents.

The production Redis pillars: maxmemory policy, persistence, high availability and monitoring

Incident #1: "Redis is using how much memory?"

Unconfigured, Redis grows until the OS kills it — taking every session, queue job and lock with it. Two lines prevent this:

# redis.conf
maxmemory 6gb                      # ~75% of the box; Redis needs headroom for
                                   # copy-on-write during persistence forks
maxmemory-policy allkeys-lru       # for a CACHE instance

The policy is a real decision, not a default to copy:

  • allkeys-lru / allkeys-lfu — evict anything, least-recently/frequently used first. Correct for pure caches. LFU is better when your access pattern has stable hot keys and long tails.
  • volatile-lru — evict only keys that have TTLs. Tempting middle ground; in practice it means one forgotten no-TTL key family slowly strangles everything that does expire.
  • noeviction — writes fail when full. Sounds bad, is correct for queues, locks, and sessions: a loud write error beats silently evicting someone's job. This is the policy conflict from the previous post that forces cache and durable workloads onto separate instances.

While you're here: lazyfree-lazy-eviction yes and friends move big deletions off the main thread — free latency insurance on modern Redis.

Incident #2: "Redis restarted and everything is gone"

Persistence has two mechanisms, and the right answer is usually a blend:

# RDB: point-in-time snapshots — compact, fast restarts, loses minutes
save 900 1
save 300 100
save 60 10000

# AOF: append-only log of writes — loses ≤1 second, bigger files, slower restart
appendonly yes
appendfsync everysec               # the sane middle: 'always' murders throughput,
                                   # 'no' trusts the OS more than I do
aof-use-rdb-preamble yes           # hybrid format: RDB snapshot + AOF tail

Decision guide by workload: pure cache — persistence off entirely is legitimate (cold cache after restart beats fork overhead), just make sure the app survives a cold start without a stampede. Queues/locks/sessions — AOF everysec, non-negotiable. The trap: persistence forks the process, and copy-on-write can briefly double memory under write-heavy load. That's why maxmemory sits at ~75%, and why "Redis got OOM-killed during the backup" is such a classic. If you see latest_fork_usec climbing in INFO, your snapshots are hurting.

Incident #3: "the primary died and nobody was promoted" (or worse: two were)

High availability options, in order of increasing machinery:

  1. Replication alone — a replica plus manual failover. Honest, simple, fine when minutes of downtime are acceptable and a human is around. Set min-replicas-to-write 1 so a lonely primary stops accepting writes it can't replicate.
  2. Sentinel — three+ sentinel processes monitor and auto-promote. The workhorse for single-shard setups. Rules: sentinels on separate failure domains (not all on the Redis boxes), an odd number of them, and clients that actually speak Sentinel (most do; verify — connecting to a static IP defeats the whole thing).
  3. Cluster — sharding + HA when the dataset outgrows one primary. It changes application semantics: multi-key operations and Lua scripts only work when keys share a slot (hash tags {user:42}:profile), and some libraries handle redirects poorly. Adopt it for data size, not reflexively for HA — Sentinel covers HA with far less complexity.

Whichever tier: rehearse the failover. Kill the primary in staging and watch. The gap between "we have Sentinel" and "failover actually works with our client libraries and timeouts" is where the 3 a.m. incident lives.

The five numbers on my Redis dashboard

INFO stats / memory / persistence — feed these to your metrics stack:
1. used_memory vs maxmemory        → the eviction cliff, seen in advance
2. evicted_keys rate               → nonzero on a queue instance = incident
3. keyspace hit rate               → cache honesty (per INFO: hits/(hits+misses))
4. connected_clients + blocked     → connection leaks announce themselves here
5. latest_fork_usec                → persistence pain, before users feel it

Plus SLOWLOG GET in your runbook — the single-threaded model means one careless KEYS * or fat Lua script stalls everyone; slowlog names the culprit. Wire the metrics into the same observability stack as everything else, and give the queue-Redis an alert distinct from the cache-Redis — their "bad" looks completely different.

The checklist

  • maxmemory set, policy chosen per workload, cache and durable data on separate instances ✔
  • AOF everysec on anything you'd miss; persistence consciously off on pure cache ✔
  • Sentinel (or your cloud's managed equivalent) with rehearsed failover ✔
  • Protected: requirepass/ACLs on, no public bind, rename-command FLUSHALL "" if you're paranoid (be paranoid) ✔
  • Five metrics + slowlog wired to dashboards and alerts ✔

Managed Redis (ElastiCache, Upstash & co.) makes several of these someone else's checkbox — but maxmemory policy, instance separation and client-side failover behavior remain yours. If your Redis is one instance doing five jobs with default everything, let's fix it before it fixes itself.

Keep reading

Related articles