Siddhant Deval
Siddhant Deval
backend24 min read

Production Redis: Observability, Security, Connection Management & the Valkey Decision

Achieving sub-millisecond p99 Redis latency in production requires active observability via SLOWLOG and LATENCY HISTORY, a deliberate ACL and TLS security posture, correct connection pool sizing, and in 2025 an explicit decision on whether to stay on Redis or migrate to Valkey. This capstone covers every operational concern that bridges Redis internals to production-grade systems.

Series·Part 7 of 7

Redis Mastery

Production Redis: Observability, Security, Connection Management & the Valkey Decision

Redis is not a cache you bolt onto a slow database — it is a data structure server with a precisely bounded contract: sub-millisecond latency, in-memory semantics, and optional persistence. The six preceding articles in this series have established that contract from the inside out: encoding internals, persistence durability, replication semantics, cluster topology, messaging primitives, and atomicity guarantees. This final article is about operating Redis against that contract in production — detecting when it is being violated before a pager fires, securing it against the most common breach vectors, sizing connection pools to avoid connection storm failures, and making the 2025 licensing decision between Redis and Valkey with precise context rather than marketing noise.

Architectural Note

This is Part 7 of the Redis Mastery series — the production operations capstone.

On-ramp: Eviction policies (allkeys-lru, allkeys-lfu, volatile-ttl, noeviction), approximate sampled LRU mechanics, jemalloc fragmentation, and activedefrag are covered in depth in High-Concurrency Cache Hazards §4. This article does not repeat that material — read that article first for the memory management foundation. This article covers the observability and security layer that operates above memory management.


1. Security: ACLs, TLS & Network Hardening

1.1 The Most Common Redis Breach Vector

Before covering advanced observability, the most impactful security action is the simplest: do not expose Redis to the public internet.

The most prevalent Redis security breach is not a sophisticated attack — it is a misconfigured bind directive:

INI
# ❌ Default on some distributions — exposes Redis on all interfaces
# (no authentication, no TLS, no firewall = instant compromise)
bind 0.0.0.0

# ✅ Production: bind to loopback and private network interface only
bind 127.0.0.1 10.0.0.1

Shodan regularly indexes tens of thousands of exposed Redis instances. An attacker who reaches an unauthenticated Redis instance can: read all keys (credential theft, session hijacking), write arbitrary data (cache poisoning, session fixation), and on older Redis versions execute OS commands via CONFIG SET dir + SAVE to write an SSH key or cron job.

1.2 requirepass vs ACL (Redis 6.0+)

requirepass (the pre-6.0 authentication mechanism) sets a single global password. All clients share the same credentials — no per-service isolation, no command restrictions, no key-pattern scoping.

INI
# ❌ Legacy: single password for all clients
# redis.conf (pre-Redis 6.0 style)
requirepass my-global-password-everyone-shares

Redis 6.0 introduced Access Control Lists (ACLs): named user accounts with individually scoped commands, key patterns, and passwords.

BASH
# ✅ Redis 6.0+ ACL: create per-service accounts

# Create a read-only cache service account
redis-cli ACL SETUSER cache-service on >cache-svc-secret \
  ~cache:* \           # Can only access keys matching cache:*
  +GET +MGET +SCAN \   # Read commands only
  -@all                # Deny everything not explicitly allowed

# Create a write-enabled session service account
redis-cli ACL SETUSER session-service on >session-svc-secret \
  ~session:* \         # Can only access keys matching session:*
  +GET +SET +DEL +EXPIRE +TTL  # Specific commands only

# Create an admin account for operations
redis-cli ACL SETUSER ops-admin on >ops-admin-secret \
  ~* \          # All keys
  +@all         # All commands

# Disable the default user (critical — default user has full access)
redis-cli ACL SETUSER default off

# Inspect current ACL
redis-cli ACL LIST
# user default off nopass ~* &* +@all  ← now disabled
# user cache-service on ... ~cache:* +GET +MGET +SCAN
# user session-service on ... ~session:* +GET +SET +DEL +EXPIRE +TTL
Crucial Requirement

Disable the default user in production. The default user has nopass (no password required) and full access in a freshly installed Redis instance. In Redis 6.0+, explicitly set redis-cli ACL SETUSER default off and create named accounts for every service. Any client attempting to connect without authentication receives NOAUTH and cannot issue commands.

BASH
# Monitor failed authentication attempts
redis-cli ACL LOG
# 1) 1) "count" 2) "3"
#    3) "reason" 4) "auth"        ← authentication failure
#    5) "context" 6) "toplevel"
#    7) "object" 8) "AUTH"
#    9) "username" 10) "attacker"
#    11) "age-seconds" 12) "0.042"
#    13) "client-info" 14) "id=9 addr=203.0.113.1:44231 ..."

1.3 Disabling Dangerous Commands

Even with ACLs, it is worth removing dangerous commands from the command table entirely for services that will never need them:

INI
# redis.conf — rename dangerous commands to empty string (effectively disables them)
rename-command FLUSHALL    ""    # No service should be able to flush all keys in production
rename-command FLUSHDB     ""    # Same
rename-command CONFIG      ""    # Prevents runtime config modification (except by ops)
rename-command DEBUG       ""    # Prevents DEBUG RELOAD, DEBUG SLEEP, etc.
rename-command SHUTDOWN    ""    # Prevents clients from shutting down the server
Performance / Safety Warning

rename-command applies globally — if you rename CONFIG to "", you cannot use CONFIG GET or CONFIG SET from any client, including redis-cli from the ops account. Use this for commands that should only ever be invoked at the OS level by operators, not from application clients.

1.4 TLS: Encrypting Data in Transit

Redis 6.0 added native TLS support. Without TLS, all Redis commands and responses travel over the wire in plaintext — including authentication passwords and session data.

INI
# redis.conf — TLS configuration (Redis 6.0+)

# Listen on the TLS port (keep non-TLS port for local connections or disable it)
tls-port 6380
port 0               # Disable plaintext port entirely

# Certificate and key
tls-cert-file /etc/ssl/redis/redis.crt
tls-key-file  /etc/ssl/redis/redis.key

# CA certificate for mutual TLS (mTLS — clients must present certificates)
tls-ca-cert-file /etc/ssl/redis/ca.crt
tls-auth-clients yes    # Require client certificates (mTLS)

# TLS version and cipher constraints
tls-protocols "TLSv1.2 TLSv1.3"
tls-ciphers "ECDH+AESGCM:ECDH+CHACHA20"
tls-prefer-server-ciphers yes
TYPESCRIPT
// ioredis TLS client connection
import Redis from 'ioredis'
import { readFileSync } from 'fs'

const redis = new Redis({
  host: 'redis.internal',
  port: 6380,
  tls: {
    cert: readFileSync('/etc/ssl/client/client.crt'),
    key:  readFileSync('/etc/ssl/client/client.key'),
    ca:   readFileSync('/etc/ssl/redis/ca.crt'),
    rejectUnauthorized: true,   // Verify server certificate
  },
  username: 'session-service',
  password: process.env.REDIS_PASSWORD,
})

2. Observability: INFO, SLOWLOG & LATENCY HISTORY

2.1 INFO all: The Primary Health Dashboard

BASH
redis-cli INFO all | grep -E "used_memory:|mem_fragmentation|connected_clients|blocked_clients|total_commands|instantaneous_ops|keyspace_hits|keyspace_misses|rdb_bgsave|aof_enabled|master_repl|role"

Key metrics to alert on:

Metric Healthy range Alert threshold
used_memory Below 70% of maxmemory > 85% — eviction pressure imminent
mem_fragmentation_ratio 1.0–1.5 > 2.0 or < 1.0
connected_clients Within pool sizing budget > 80% of maxclients
blocked_clients 0 > 0 for > 5 seconds
instantaneous_ops_per_sec Baseline ± 30% Spike or cliff — indicates workload shift or failure
keyspace_hit_rate hits / (hits + misses) < 90% for cache workloads — investigate miss patterns
rdb_last_bgsave_status ok err — persistence failure, data at risk
BASH
# Cache hit rate calculation
redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"
# keyspace_hits:   8432910
# keyspace_misses: 421642

# hit_rate = hits / (hits + misses) = 8432910 / (8432910 + 421642) = 95.2%

2.2 SLOWLOG: Finding the Expensive Commands

The slow log captures every command that exceeds the slowlog-log-slower-than threshold (measured in microseconds).

INI
# redis.conf — aggressive slow log settings for production
slowlog-log-slower-than 1000    # Capture commands slower than 1ms (1000 microseconds)
                                # Default is 10000 (10ms) — misses most real regressions
slowlog-max-len 256             # Keep the last 256 slow log entries (ring buffer)
BASH
# View the 10 most recent slow log entries
redis-cli SLOWLOG GET 10
# 1) 1) (integer) 42              ← unique log entry ID
#    2) (integer) 1725350400      ← Unix timestamp
#    3) (integer) 8431            ← duration in microseconds (8.4ms)
#    4) 1) "EVAL"                 ← command
#       2) "local x = ..."        ← script body (truncated)
#       3) "1"
#       4) "rate:user:42"
#    5) "127.0.0.1:52341"        ← client address
#    6) ""                        ← client name (if set via CLIENT SETNAME)

# Count entries in slow log
redis-cli SLOWLOG LEN
# → 47

# Clear the slow log
redis-cli SLOWLOG RESET
Pro Tip & Optimization

Set slowlog-log-slower-than 1000 (1ms) in production. The default 10ms threshold hides the latency regressions that matter most — a command at 5ms running 50,000 times per second is a 250 second per second cumulative bottleneck that the 10ms threshold never captures.

2.3 LATENCY HISTORY: Spike Analysis for Infrastructure Events

SLOWLOG captures individual slow commands. LATENCY HISTORY captures server-internal events that cause event-loop stalls — fork() for BGSAVE, AOF rewrites, disk I/O flushes:

BASH
# List all tracked latency event types
redis-cli LATENCY LATEST
# event            latest        max   avg_over_time
# fork             1725350400    52    45
# aof_fsync        1725350401    8     3
# aof_rewrite      1725350402    4200  890

# Historical spike data for fork events (timestamps + duration in ms)
redis-cli LATENCY HISTORY fork
# 1725350100  48
# 1725350400  52
# 1725350700  47

# Reset latency history
redis-cli LATENCY RESET
Event What it measures High value indicates
fork Time to fork() for BGSAVE/BGREWRITEAOF Large dataset; THP enabled; memory pressure
aof_fsync Time for AOF fsync() call Disk I/O saturation; storage latency
aof_rewrite Full AOF rewrite duration Large dataset; COW memory pressure during rewrite
command Longest individual command KEYS *, slow Lua script, or SORT on large sets
BASH
# Simulate a server blocking event for alerting pipeline testing
redis-cli DEBUG SLEEP 0.1   # Block server for 100ms
# Use only in staging — DO NOT run in production

3. Hot Key and Big Key Detection

3.1 Hot Key Detection

A "hot key" concentrates a disproportionate share of traffic on a single key — and in a Cluster topology, on the single shard that owns it.

BASH
# Detect hot keys using frequency counter (requires LFU eviction policy)
# First, set an LFU policy:
redis-cli CONFIG SET maxmemory-policy allkeys-lfu

# Then scan for hot keys using redis-cli (sampling-based)
redis-cli --hotkeys
# Scanning the entire keyspace to find hot keys as well as
# temporary TTX info or other info for all keys found...
# [28.00%] Hot key 'product:viral-item:9871' found so far
# -------- summary -------
# Sampled 2931 keys in 1.23 seconds
# hot key found with counter: 1048576   keyname: product:viral-item:9871
# hot key found with counter: 524288    keyname: session:user:1

# Per-key frequency counter (on a specific key)
redis-cli OBJECT FREQ product:viral-item:9871
# → 1048576  (logarithmic LFU counter — not raw access count)
Performance / Safety Warning

redis-cli --hotkeys uses sampling (OBJECT FREQ on random keys) — it does not scan the entire keyspace. False negatives are possible for keys with bursty but infrequent access. For guaranteed hot key detection, instrument your application client to track per-key call frequency client-side and alert when any key exceeds N% of total Redis call volume.

Hot key mitigation in Cluster: Hot keys in Redis Cluster receive all their traffic on one shard — other shards sit idle. Horizontal scaling does not help. The fixes are architectural:

  1. Key salting (covered in Caching Topologies §4.3) — distribute reads across N copies of the key
  2. In-process L1 caching — serve ultra-hot keys from application heap (0 RTT) with Redis RESP3 invalidation for consistency

3.2 Big Key Detection

A "big key" is a single key whose value consumes a large amount of memory. Big keys cause EXPIRE, DEL, UNLINK operations to be slow, and a blocking DEL stalls the event loop for its full serialization duration.

BASH
# Scan for big keys (samples 100 keys per type by default — run during off-peak)
redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type.
# ...
# -------- summary -------
# Biggest string found 'cache:product:catalog:all' has 2097152 bytes (2MB)
# Biggest hash found 'user:aggregated:stats' has 84231 fields
# Biggest list found 'event:log:2026-09' has 2341098 items
# Biggest set found 'feature:flags:all' has 9821 members
# Biggest zset found 'leaderboard:global' has 4500000 members

# Inspect a specific key's memory footprint
redis-cli MEMORY USAGE leaderboard:global
# → 489123456  (bytes — ~467MB for a 4.5M member sorted set)
Key size Risk Mitigation
> 1MB String Blocking DEL / EXPIRE stall Use UNLINK (async delete) instead of DEL
> 10K Hash fields HGETALL network transfer overhead Paginate with HSCAN cursor COUNT 100
> 100K List items LRANGE 0 -1 full transfer Use LRANGE with explicit bounds; paginate
> 1M Sorted Set members ZRANGE 0 -1 transfer; slow ZADD at scale Shard into multiple ZSETs by key range
BASH
# ✅ Always use UNLINK instead of DEL for large keys
redis-cli UNLINK leaderboard:global
# UNLINK is asynchronous — key is removed from keyspace immediately
# but memory is reclaimed by a background thread (no event loop stall)

# OBJECT ENCODING to understand memory layout before deletion
redis-cli OBJECT ENCODING leaderboard:global
# → "skiplist"  (4.5M members — hashtable + skiplist dual structure from Part 1)

4. Connection Management

4.1 Pool Sizing

Redis is single-threaded for command execution. More connections do not increase throughput — they increase queuing. The optimal pool size is:

$$\text{pool size per service instance} = \lceil \text{concurrent_requests_peak} \times \text{redis_latency_sec} \rceil + \text{headroom}$$

Service profile Pool size recommendation
Node.js (single-threaded, async) 10–50 connections per pod
Go / Java (multi-threaded, blocking I/O) thread_pool_size × redis_call_fraction
High-throughput pipeline workloads 1–5 connections per pipeline worker
INI
# redis.conf — connection limits
maxclients 10000    # Default: 10000 simultaneous client connections
# Redis accepts up to maxclients connections; above this, new connections receive:
# ERR max number of clients reached

# Idle connection timeout (0 = never timeout)
timeout 300         # Close connections idle for 300 seconds
tcp-keepalive 300   # TCP keepalive interval — detects dead connections at OS level

4.2 CLIENT Commands for Connection Auditing

BASH
# List all connected clients
redis-cli CLIENT LIST
# id=1 addr=10.0.0.10:52341 laddr=10.0.0.1:6379 fd=8 name=session-svc age=0 idle=0
#   flags=N db=0 sub=0 psub=0 multi=-1 watch=0 qbuf=0 qbuf-free=20482
#   argv-mem=10 multi-mem=0 tot-mem=22338 rbs=16384 rbp=0
#   obl=0 oll=0 omem=0 events=r cmd=client|list user=session-service

# Key fields:
# idle   = seconds since last command (identify leaked/stale connections)
# flags  = b (blocked on BLPOP/BRPOP), N (normal), P (pub/sub subscriber)
# cmd    = last executed command
# user   = ACL user (requires Redis 6.0+)

# Kill a specific connection (by connection ID)
redis-cli CLIENT KILL ID 42

# Kill all connections idle > 60 seconds (cleanup stale connections)
redis-cli CLIENT KILL SKIPME no IDLE 60

# Set a name for the current connection (visible in CLIENT LIST)
redis-cli CLIENT SETNAME "session-service-pod-3"

# CLIENT NO-EVICT (Redis 7.2+): protect this connection from client eviction
# when Redis is at maxclients — use for critical ops/monitoring connections
redis-cli CLIENT NO-EVICT ON

5. The 2025 Valkey Decision

5.1 Three Active License Tracks

In March 2024, Redis Ltd changed the license for Redis 7.4+ from BSD to a dual SSPL/RSALv2 license. Redis 8.0 (released 2025) moved to AGPL. The licensing landscape is now:

Version range License Source Key implication
≤ 7.2.x BSD 3-Clause Redis Ltd Open source — use freely
7.4.x – 7.6.x SSPL + RSALv2 Redis Ltd Cloud provider hosting restricted
8.0+ AGPL v3 Redis Ltd Copyleft — modifications must be open-sourced
Valkey 7.2+ Apache 2.0 Linux Foundation Open source — cloud provider fork

5.2 What Is Valkey?

In response to the March 2024 license change, AWS, Google Cloud, Oracle, Ericsson, Snap, and others forked Redis 7.2 under the Linux Foundation umbrella to create Valkey — released under Apache 2.0.

Concern Redis (Redis Ltd) Valkey (Linux Foundation)
Wire protocol RESP3 RESP3 (fully compatible)
Client libraries All existing Redis clients All existing Redis clients (no changes needed)
Commands Full Redis 7.x command set Full Redis 7.2 command set + Valkey additions
Performance Redis 8.0: multi-threaded I/O Valkey 8.0: multi-threaded I/O (parallel development)
License AGPL (8.0) Apache 2.0
Managed cloud Redis Cloud (Redis Ltd) AWS ElastiCache/Valkey, GCP Memorystore/Valkey, Azure Cache
Crucial Requirement

If you are running AWS ElastiCache, GCP Memorystore, or Azure Cache for Redis today: check your current engine version. AWS ElastiCache released Valkey 7.2 and 8.0 as managed options in 2024. GCP Memorystore for Valkey is generally available. If your managed Redis instance is running 7.2+, you may already be running Valkey — or will be migrated to it by your cloud provider as they transition away from the SSPL/AGPL licensed versions.

5.3 Migration Path: Is It Breaking?

Valkey is wire-protocol compatible with Redis 7.2. No client library changes are required. No application code changes are required. The migration is:

  1. Point your connection string at a Valkey endpoint
  2. Verify your client library version supports Valkey (most do — they speak RESP3, not a Redis-specific protocol)
  3. Test command compatibility (Valkey ≥ 7.2 supports the full Redis 7.2 command surface)
TYPESCRIPT
// No code change needed — same client, same API
// Before (Redis 7.2):
const redis = new Redis({ host: 'redis.us-east-1.cache.amazonaws.com', port: 6379 })

// After (Valkey 7.2 on ElastiCache):
const redis = new Redis({ host: 'valkey.us-east-1.cache.amazonaws.com', port: 6379 })
// Same ioredis client. Same commands. Same behavior.

5.4 Decision Matrix

Team profile Recommendation
Self-hosted, need latest Redis features (Redis 8.0+) Evaluate AGPL implications for your codebase; if internal use only, AGPL applies but does not force open-sourcing your app
Self-hosted, open source acceptable Valkey — Apache 2.0, active Linux Foundation governance, compatible
Managed cloud (AWS / GCP / Azure) Switch to cloud provider's Valkey offering — same SLAs, same API, no licensing cost
Redis Enterprise customer Redis Ltd commercial license — enterprise features (multi-region active-active, Modules) are only available commercially

6. Managed Service Selection

Feature AWS ElastiCache (Valkey/Redis) GCP Memorystore (Valkey/Redis) Azure Cache for Redis
Cluster mode ✅ (ElastiCache Cluster) ✅ (Memorystore Cluster) ✅ (Enterprise tier)
TLS
Automated backups ✅ (RDB to S3) ✅ (RDB to GCS)
RESP3 / Client-side caching ✅ (Redis 7.0+ / Valkey) ✅ (Enterprise)
Valkey option ✅ (2024+) ✅ (2024+) ❌ (Redis only as of 2025)
Multi-AZ failover
Global replication ✅ (Global Datastore) ✅ (Cross-region replication) ✅ (Geo-replication, Enterprise)
When self-hosted wins Sub-ms p99 SLA requirements; specific hardware tuning; cost at extreme scale (> 1TB RAM)

Summary

Concept Rule
Network hardening Bind to 127.0.0.1 and private IPs only. bind 0.0.0.0 + no auth = instant compromise.
ACLs Disable the default user. Create per-service accounts scoped to minimum commands + key patterns. Use ACL LOG to monitor auth failures.
Dangerous commands rename-command FLUSHALL "" and rename-command CONFIG "" in redis.conf. Remove the ability, not just the permission.
TLS Enable tls-port with tls-cert-file + tls-key-file + tls-ca-cert-file. Set port 0 to disable plaintext.
SLOWLOG Set slowlog-log-slower-than 1000 (1ms). The default 10ms threshold hides most real-world latency regressions.
LATENCY HISTORY Monitor fork, aof_fsync, and aof_rewrite events. Fork spikes > 50ms indicate THP is enabled or dataset too large for single node.
Hot keys in Cluster Concentrate 100% of a key's traffic on one shard — no horizontal scaling relief. Fix architecturally via key salting or L1 caching.
Big keys Use UNLINK not DEL. Paginate reads with HSCAN/SSCAN/ZSCAN. Detect with redis-cli --bigkeys during off-peak hours.
Connection pools Pool size ≈ concurrent_peak_requests × redis_latency. Use CLIENT LIST + CLIENT KILL IDLE to audit and prune stale connections.
Valkey (2025) Wire-compatible with Redis 7.2. Apache 2.0. Cloud providers (AWS, GCP) now default to Valkey. Migration requires only a connection string change.

Series Complete

This article closes the Redis Mastery series. The seven parts have traced the canonical failure mode — "OK does not mean I will remember this under failure" — through every layer of the Redis operational stack:

Part The guarantee Redis makes (and doesn't)
1. Data Structures Redis silently promotes encodings at thresholds — memory and latency implications are invisible without OBJECT ENCODING.
2. Persistence OK does not survive a restart without persistence. Each persistence mode has a distinct data-loss window.
3. Replication A replica does not eliminate data loss — async replication means the primary can acknowledge and then crash before the replica receives the write.
4. Cluster Cluster adds sharding but introduces redirect semantics (MOVED/ASK) and the full-coverage default that takes down the entire cluster on one shard failure.
5. Pub/Sub vs Streams Pub/Sub delivers no durability. Streams deliver at-least-once. Choose based on whether message loss is explicitly acceptable.
6. Atomicity MULTI/EXEC provides isolation, not rollback. Runtime errors partially apply. Lua provides true atomicity at the cost of blocking the event loop.
7. Production Ops Every guarantee from Parts 1–6 is only discoverable through active observability, correctly configured security, and an intentional deployment decision.
Research & Synthesis Note

This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.

#Redis#Valkey#Production Engineering#Observability#Security
Siddhant Deval

Written by Siddhant Deval

Senior Full-Stack Engineer building high-scale architectures, browser performance engineering systems, and SaaS platforms.