What's Actually Happening

Redis memory usage grows continuously and reaches high levels. Memory is not being freed, potentially causing performance issues or crashes.

The Error You'll See

```bash $ redis-cli INFO memory

used_memory: 8589934592 used_memory_human: 8.00G maxmemory: 10737418240 maxmemory_human: 10.00G ```

Memory warning:

bash
WARNING: Redis is using more than 90% of configured maxmemory

OOM error:

bash
OOM command not allowed when used memory > 'maxmemory'

Eviction errors:

bash
Error: MISCONF Redis is configured to save RDB snapshots

Memory fragmentation:

```bash $ redis-cli INFO memory

mem_fragmentation_ratio: 2.5 ```

Why This Happens

  1. 1.Keys without expiration - Keys never expire, accumulate over time
  2. 2.Large values stored - Large strings, lists, or sets consuming memory
  3. 3.Wrong eviction policy - No-eviction policy doesn't remove keys
  4. 4.Memory fragmentation - Allocator fragmentation overhead
  5. 5.No maxmemory limit - Redis can use unlimited memory
  6. 6.Client buffers - Large client output buffers

Step 1: Check Redis Memory Stats

```bash # Check memory usage: redis-cli INFO memory

# Key metrics: # used_memory - Current memory usage in bytes # used_memory_human - Human readable usage # used_memory_peak - Peak memory usage # maxmemory - Configured limit # mem_fragmentation_ratio - Fragmentation overhead

# Check memory usage percentage: redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"

# Check peak memory: redis-cli INFO memory | grep used_memory_peak_human

# Calculate usage percentage: redis-cli --eval memory-percent.lua

# Check fragmentation ratio: redis-cli INFO memory | grep mem_fragmentation_ratio # Should be around 1.0-1.5 # > 1.5 indicates significant fragmentation

# Check total system memory: free -h ```

Step 2: Identify Memory Consumers

```bash # Find largest keys: redis-cli --bigkeys

# Sample output: # [00.00%] Biggest string found 'key1' has 10000 bytes # [50.00%] Biggest list found 'list1' has 5000 items

# Find keys by pattern: redis-cli KEYS "session:*" | head -100

# Count keys by pattern: redis-cli --eval count-keys.lua , "session:*"

# Check specific key size: redis-cli DEBUG OBJECT key1 # Or: STRLEN, LLEN, HLEN, SCARD depending on type

# Get key memory usage: redis-cli MEMORY USAGE key1

# Analyze key types: redis-cli --eval analyze-keys.lua

# Check database size: redis-cli DBSIZE ```

Step 3: Check Key Expiration

```bash # Check keys with TTL: redis-cli TTL key1 # -1 = no expiration (never expire) # -2 = key doesn't exist # > 0 = seconds until expiration

# Find keys without expiration: redis-cli --scan | while read key; do ttl=$(redis-cli TTL "$key") if [ "$ttl" == "-1" ]; then echo "$key" fi done

# Set expiration on keys: redis-cli EXPIRE key1 3600

# Set expiration in milliseconds: redis-cli PEXPIRE key1 3600000

# Set expiration at specific time: redis-cli EXPIREAT key1 1704067200

# Check expiration stats: redis-cli INFO stats | grep expired_keys

# Keys expiring per second: redis-cli --eval expiration-rate.lua ```

Step 4: Configure Eviction Policy

```bash # Check current policy: redis-cli CONFIG GET maxmemory-policy

# Available policies: # noeviction - Don't evict, return errors (default) # allkeys-lru - Evict least recently used keys # volatile-lru - Evict LRU among keys with expiration # allkeys-lfu - Evict least frequently used keys # volatile-lfu - Evict LFU among keys with expiration # allkeys-random - Evict random keys # volatile-random - Evict random keys with expiration # volatile-ttl - Evict keys with shortest TTL

# Set eviction policy: redis-cli CONFIG SET maxmemory-policy allkeys-lru

# For temporary data (sessions, cache): redis-cli CONFIG SET maxmemory-policy volatile-lru

# Set maxmemory limit: redis-cli CONFIG SET maxmemory 4gb

# Or in redis.conf: maxmemory 4gb maxmemory-policy allkeys-lru

# Restart Redis if needed: systemctl restart redis ```

Step 5: Set Key Expiration on Write

```bash # Set key with expiration: redis-cli SET key1 value1 EX 3600

# Or separate commands: redis-cli SET key1 value1 redis-cli EXPIRE key1 3600

# For hashes: redis-cli HSET hash1 field1 value1 redis-cli EXPIRE hash1 3600

# For lists: redis-cli LPUSH list1 item1 redis-cli EXPIRE list1 3600

# For sets: redis-cli SADD set1 member1 redis-cli EXPIRE set1 3600

# Application code pattern: # Python: r.set('session:user1', data, ex=3600)

# Node.js: client.set('session:user1', data, 'EX', 3600)

# Always set TTL for cache/session data ```

Step 6: Analyze Key Types

```bash # Check key type: redis-cli TYPE key1

# Types: string, list, set, zset, hash, stream

# Get size by type: # String: redis-cli STRLEN key1

# List: redis-cli LLEN key1

# Hash: redis-cli HLEN key1

# Set: redis-cli SCARD key1

# Sorted Set: redis-cli ZCARD key1

# Estimate memory for each type: # String: ~100 bytes overhead + value size # List: ~100 bytes overhead + 64 bytes per item # Hash: ~100 bytes overhead + 64 bytes per field # Set: ~100 bytes overhead + 64 bytes per member

# Check encoding: redis-cli OBJECT ENCODING key1 # Can be: raw, embstr, int, ziplist, hashtable, etc. ```

Step 7: Clean Up Unused Keys

```bash # Delete keys by pattern: redis-cli --scan --pattern "old:*" | xargs redis-cli DEL

# Delete keys without expiration: redis-cli --scan | while read key; do ttl=$(redis-cli TTL "$key") if [ "$ttl" == "-1" ]; then redis-cli DEL "$key" fi done

# Delete large keys: redis-cli --bigkeys | grep "Biggest" | awk '{print $4}' | tr "'" "" | xargs redis-cli DEL

# Delete old sessions: redis-cli --scan --pattern "session:*" | while read key; do redis-cli DEL "$key" done

# Use UNLINK for async deletion (Redis 4.0+): redis-cli --scan --pattern "temp:*" | xargs redis-cli UNLINK

# Flush specific database: redis-cli -n 1 FLUSHDB

# Note: FLUSHALL deletes all databases ```

Step 8: Configure Memory Limits

```bash # In redis.conf:

# Set max memory: maxmemory 4gb

# Set eviction policy: maxmemory-policy allkeys-lru

# Set sample size for eviction: maxmemory-samples 5

# Disable persistence if not needed: save "" # Or reduce frequency: save 900 1 save 300 10 save 60 10000

# Disable replicas if not needed: replicaof no one

# Reduce client buffers: client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60

# Apply changes: redis-cli CONFIG REWRITE ```

Step 9: Check Client Buffers

```bash # Check client list: redis-cli CLIENT LIST

# Check output buffer sizes: redis-cli CLIENT LIST | grep -E "omem|qmem"

# Check blocked clients: redis-cli CLIENT LIST | grep blocked

# Kill clients with large buffers: redis-cli CLIENT LIST | while read line; do omem=$(echo $line | grep -oP 'omem=\K\d+') if [ "$omem" -gt 10000000 ]; then id=$(echo $line | grep -oP 'id=\K\d+') redis-cli CLIENT KILL ID $id fi done

# Check pub/sub subscribers: redis-cli PUBSUB CHANNELS redis-cli PUBSUB NUMSUB channel1

# Configure buffer limits: redis-cli CONFIG SET client-output-buffer-limit "normal 0 0 0" redis-cli CONFIG SET client-output-buffer-limit "replica 256mb 64mb 60" ```

Step 10: Redis Memory Verification Script

```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-redis-memory.sh #!/bin/bash

REDIS_HOST=${1:-"localhost"} REDIS_PORT=${2:-"6379"}

echo "=== Memory Usage ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep -E "used_memory_human|used_memory_peak_human|maxmemory_human|mem_fragmentation_ratio"

echo "" echo "=== Memory Limits ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET maxmemory redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET maxmemory-policy

echo "" echo "=== Key Statistics ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO stats | grep -E "expired_keys|evicted_keys"

echo "" echo "=== Biggest Keys ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT --bigkeys

echo "" echo "=== Keys Without Expiration ===" count=0 redis-cli -h $REDIS_HOST -p $REDIS_PORT --scan | head -100 | while read key; do ttl=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT TTL "$key") if [ "$ttl" == "-1" ]; then echo "$key (no TTL)" count=$((count + 1)) fi done | head -20 echo "Total checked: 100"

echo "" echo "=== Client Buffers ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT CLIENT LIST | awk '{for(i=1;i<=NF;i++) if($i~/^(id|omem|qmem)=/) print $i}' | head -20

echo "" echo "=== Persistence Config ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET save EOF

chmod +x /usr/local/bin/check-redis-memory.sh

# Usage: /usr/local/bin/check-redis-memory.sh localhost 6379

# Monitor: watch -n 30 /usr/local/bin/check-redis-memory.sh ```

Redis Memory Checklist

CheckCommandExpected
Memory usageINFO memoryBelow maxmemory
Fragmentationmem_fragmentation_ratio< 1.5
Eviction policyCONFIG GET maxmemory-policyAppropriate for use case
Key expirationTTL keysKeys have TTL for cache
Client buffersCLIENT LISTNo large buffers

Verify the Fix

```bash # After fixing memory usage

# 1. Check memory usage redis-cli INFO memory | grep used_memory_human // Below maxmemory

# 2. Check fragmentation redis-cli INFO memory | grep mem_fragmentation_ratio // Around 1.0-1.5

# 3. Monitor over time # Wait 1 hour redis-cli INFO memory | grep used_memory_human // Stable or declining

# 4. Check evicted keys redis-cli INFO stats | grep evicted_keys // Eviction working if needed

# 5. Check TTL on keys redis-cli TTL session:user1 // Has expiration

# 6. Verify application # Run application // No memory errors ```

  • [Fix Redis Connection Refused](/articles/fix-redis-connection-refused)
  • [Fix Redis High Latency](/articles/fix-redis-high-latency)
  • [Fix Redis Key Not Found](/articles/fix-redis-key-not-found)