INFO memory field-by-field, MEMORY USAGE and MEMORY DOCTOR, scanning for oversized keys, encoding threshold tuning, active defragmentation configuration, and a production workflow for diagnosing unexpected memory growth.
P-4 — Memory Profiling and Optimization
Who this module is for: Redis is using more RAM than expected and you do not know why. Or you are designing a Redis schema and want to estimate memory costs before deploying. This module covers the full suite of Redis memory inspection tools and the optimizations that consistently recover the most RAM in production.
The Memory Audit Starting Point: INFO memory
Every Redis memory investigation starts here:
INFO memory
Interpreting the Key Fields
mem_fragmentation_ratio = used_memory_rss / used_memory
- < 1.0 → Redis is using swap (critical, investigate immediately)
- 1.0–1.2 → healthy
- 1.2–1.5 → moderate fragmentation (normal for dynamic workloads)
-
1.5 → high fragmentation — consider
activedefragor restart
used_memory_overhead = memory used by Redis's internal data structures (the global keyspace dict, expiry table, per-client buffers). If this is a large fraction of used_memory, you have very small values (overhead dominates) — consider consolidating keys into Hashes.
mem_clients_normal = memory used by client output buffers. If this is large (> 10MB), you may have slow clients receiving data faster than they can consume it.
MEMORY USAGE: Per-Key Cost
MEMORY USAGE key [SAMPLES count]
Returns the exact number of bytes allocated for a key and its value, including all internal structures (robj, SDS, listpack nodes, etc.).
For collections, SAMPLES controls how many elements are sampled to estimate total cost (default 5). Use SAMPLES 0 for exact measurement on small collections.
Using MEMORY USAGE to find expensive keys:
On a production instance with millions of keys, sample a representative subset:
MEMORY DOCTOR
MEMORY DOCTOR
Returns a human-readable diagnosis. Possible outputs:
Or for a healthy instance:
"Sam, I have detected no problems in the server memory subsystem."
Not a substitute for INFO memory, but a quick sanity check.
MEMORY MALLOC-STATS
MEMORY MALLOC-STATS
Dumps the full jemalloc allocator statistics — bin sizes, fragmentation per bin, active vs retained pages. Useful when you suspect allocator-level fragmentation rather than Redis-level issues.
Finding the Memory Culprits
Pattern 1: Large Hashes in hashtable Encoding
A Hash with > 128 fields (or any field > 64 bytes) switches from listpack to hashtable encoding. The memory cost jumps roughly 5x per element. Find them:
If you find 1,000 user Hashes in hashtable encoding that should be in listpack encoding (they have < 128 fields), your encoding threshold is wrong. Check:
If the threshold is already 128 but hashes have 50 fields in hashtable encoding, some field values exceed 64 bytes. Identify them with HGETALL on a sample key.
Pattern 2: Keys Without TTL (Orphaned Data)
Or in a Redis script:
If expires is much less than keys, most of your keys have no TTL. For a cache, this means eviction will eventually clear them — but you are paying for that memory until then. For an application database, this is expected.
Pattern 3: String Keys Storing JSON When Hashes Would Be Better
OBJECT ENCODING tells you if a String key is in raw encoding (large string). If it is storing a JSON blob, consider whether you update individual fields — if so, a Hash is more efficient and enables atomic partial updates.
Pattern 4: Sorted Set Keys in skiplist Encoding
Sorted Sets with > 128 members use skiplist encoding. A skiplist + hashtable for 1,000 members uses ~250KB; listpack for the same 1,000 members uses ~55KB. If you have many small-to-medium sorted sets exceeding the listpack threshold by a few members:
CONFIG SET zset-max-listpack-entries 256 → raise threshold if members are ≤ 64 bytes
Active Defragmentation
When mem_fragmentation_ratio > 1.5, enable active defragmentation:
Active defragmentation runs a background scan, finding allocations that can be moved to compacted jemalloc pages. It uses 1–25% of a CPU core and can recover significant memory without restarting Redis.
When it is not enough: If mem_fragmentation_ratio > 2.0 and the instance has been running for months with heavy churn, active defragmentation may be slow to converge. A Redis restart (graceful shutdown → dump.rdb / AOF flush → restart → reload) resets memory layout and eliminates fragmentation instantly. Plan this during a low-traffic window.
Encoding Threshold Tuning
The single most impactful memory optimization is ensuring data structures use compact encodings.
Hash Thresholds
If your user Hashes have 50 fields with values averaging 30 bytes: raise to entries 256, value 64 to keep them in listpack. Memory reduction: ~5x per hash.
Sorted Set Thresholds
If your leaderboard sorted sets have up to 200 members under 40 bytes each: raise to entries 256.
Set Thresholds (integer sets)
set-max-intset-entries 512 → intset (sorted integer array) if all members are integers
If you are storing user IDs (integers) in Sets: intset is the most compact encoding. Ensure all members are integers to keep the intset encoding.
Testing Threshold Changes
After changing thresholds:
- New keys will use the new thresholds
- Existing keys will NOT automatically convert (they were already promoted to the larger encoding)
- To convert existing keys: DUMP + RESTORE or use
redis-cli --pipeto reload the data
The safest approach for large deployments: change thresholds and let natural key churn (TTL expiry + re-creation) gradually adopt the new encoding.
The Hash-for-Small-Objects Pattern
For reference, here is the memory comparison that drives this pattern:
The per-key overhead (robj, SDS for key, hash table entry) adds ~80 bytes per key. When values are small, this overhead dominates. Grouping small objects under a single Hash key eliminates most of it.
Implementation: Instead of SET user:1001:name "Jatin", use HSET users 1001:name "Jatin" (or one Hash per user: HSET user:1001 name "Jatin" email "...".
This pattern has limits: a Hash cannot have per-field TTLs, and you cannot atomically query across multiple user Hashes. For most use cases, the memory savings outweigh these constraints.
Memory Optimization Checklist
- Run
INFO memory— checkmem_fragmentation_ratio,used_memory_overhead - Find top 20 keys by memory:
MEMORY USAGE+SCAN - Check encoding of large collections:
OBJECT ENCODING - Identify Hashes in hashtable encoding that should be listpack:
HLEN+OBJECT ENCODING - Check for keys without TTL in a cache context:
INFO keyspace(expires vs keys count) - Review encoding thresholds:
CONFIG GET hash-max-listpack-*,zset-max-listpack-* - Consider active defragmentation if
mem_fragmentation_ratio > 1.5 - Evaluate Hash-for-small-objects pattern for high-cardinality small-value datasets
- Set
maxmemoryandmaxmemory-policyif not set (do not let Redis use unbounded RAM)
Summary
INFO memoryis the starting point: checkmem_fragmentation_ratio,used_memory_dataset,mem_clients_normalMEMORY USAGE keygives exact per-key RAM cost including all internal structuresMEMORY DOCTORfor a quick health check;MEMORY MALLOC-STATSfor allocator-level detail- Encoding inspection:
OBJECT ENCODINGreveals whether a key is in compact (listpack, intset) or large (hashtable, skiplist) encoding - Tuning encoding thresholds (
hash-max-listpack-entries,zset-max-listpack-entries) is the highest-leverage memory optimization - Active defragmentation (
activedefrag yes) recovers memory from fragmentation without restarting - The Hash-for-small-objects pattern reduces per-key overhead for high-cardinality small datasets by 5–10x
Next: P-5 — Atomic Counters, Rate Limiters, and Sliding Windows — building lock-free counters, fixed and sliding window rate limiters, and the token bucket algorithm using Redis's atomic operations.
An engineering team notices their Redis instance using 4GB of RAM (used_memory_rss), but used_memory is only 1.2GB. The instance has been running for 6 months with high key churn (constant creation and deletion of keys). What is the primary cause of this discrepancy, and what is the safest way to reclaim the memory without downtime?
A developer wants to store 500,000 user profiles. Each profile contains 10 attributes (name, email, age, etc.). They write a script that stores each attribute as a separate top-level string key: user:1001:name "Alice", user:1001:age "30", etc. Why is this structurally inefficient in Redis, and what is the standard optimization?
An application stores product catalogs in Redis Hashes. Each Hash has about 200 fields. The engineering team checks OBJECT ENCODING catalog:books and sees it is using hashtable. They want to reduce memory usage by converting these Hashes to the more efficient listpack encoding. What must they do?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion