A high Redis mem_fragmentation_ratio is one of the most common memory alerts, and one of the most misread. The number compares what the operating system sees with what Redis thinks it has allocated. A value of 1.5 on a 30 GB instance can mean 10 GB of real waste, a harmless leftover from yesterday's peak, or an accounting effect that has nothing to do with your data. A value below 1 is worse: it usually means the kernel has pushed part of Redis to swap. Here is how to read it properly and what to do in each case.
What the ratio actually measures
INFO memory defines mem_fragmentation_ratio as the ratio between used_memory_rss (what the process occupies in RAM, as the OS sees it) and used_memory (what Redis has allocated through its allocator). The documentation is explicit that it does not only include fragmentation, but also other process overheads, code, shared libraries and the stack.
Start by looking at everything together:
redis-cli INFO memory | grep -E '^(used_memory|used_memory_rss|used_memory_peak|mem_fragmentation_ratio|mem_fragmentation_bytes|allocator_frag_ratio|allocator_frag_bytes|allocator_rss_ratio|rss_overhead_ratio|mem_allocator):'
redis-cli MEMORY DOCTOR
Since Redis 4.0, MEMORY DOCTOR also gives a short human-readable report with suggestions. Then read the numbers in this order:
mem_fragmentation_bytesis the absolute difference. The documentation notes that when it is only a few megabytes, even a ratio of 1.5 or more is not a problem. Small instances often show high ratios for exactly this reason.allocator_frag_ratioandallocator_frag_bytescompare active allocator pages with what is actually allocated. The documentation calls this the true (external) fragmentation metric.allocator_rss_ratioshows pages the allocator holds but can, and probably soon will, release back to the OS.rss_overhead_ratiocovers RSS that is not allocator or heap related.
If allocator_frag_ratio is close to 1 while mem_fragmentation_ratio is high, you are not looking at fragmentation in the usual sense.
Cause 1: a past peak, not fragmentation
The most common explanation is described in the Redis memory optimization guide: Redis does not always return memory to the OS when keys are removed. If an instance grew to 5 GB and then 2 GB of keys were deleted, RSS will probably stay around 5 GB while used_memory drops to 3 GB. The freed pages often share space with keys that still exist, so the allocator cannot give them back.
This memory is not lost. When new keys arrive, the allocator reuses the freed space, and RSS stays flat until the data grows past the old size. Compare used_memory_peak with used_memory_rss: if RSS is close to the peak, the ratio reflects history rather than a problem. The guide draws the practical conclusion itself: provision memory for peak usage, and do not trust the fragmentation ratio after a large peak.
Typical triggers are a mass deletion, a batch job that filled the cache for a few hours, or a large eviction wave. We explained how eviction and maxmemory interact in Redis OOM and eviction policies.
Cause 2: real fragmentation
True external fragmentation appears when the workload keeps allocating and freeing values of different sizes: strings that grow and shrink, many keys with short TTLs, hashes and lists that change size constantly. The allocator ends up with many partly used pages. You see it as a high allocator_frag_ratio together with a large allocator_frag_bytes, and it does not go away when the data set grows back.
Note that maxmemory limits what Redis allocates for data; the memory guide says Redis allocates at most that much, apart from small extra allocations. Fragmentation comes on top. A server with maxmemory 24gb and 1.4 real fragmentation needs much more than 24 GB of RAM, and the OOM killer does not care what maxmemory says.
Cause 3: ratio below 1 means swapping
When used_memory is larger than used_memory_rss, the documentation says part of Redis memory has been swapped out by the OS, and you should expect significant latency. This is the dangerous case, and it is easy to miss because alerts are usually set only for high values. The latency guide shows how to confirm it:
redis-cli INFO server | grep process_id
grep Swap: /proc/<pid>/smaps | awk '{s+=$2} END {print s " kB swapped"}'
vmstat 1 # non-zero si/so columns mean swapping is happening now
The fix is less memory pressure, not tuning Redis: more RAM, a lower maxmemory, or moving other memory-hungry processes off the host.
Active defragmentation
Redis 4.0 introduced active defragmentation, which moves values into compact memory regions while the server keeps running. According to redis.conf, it is disabled by default and only works with the jemalloc copy bundled with Redis, which is the default for Linux builds. You can enable it at runtime:
# check that the server uses jemalloc
redis-cli INFO memory | grep mem_allocator
# enable at runtime (defaults shown for the thresholds)
redis-cli CONFIG SET activedefrag yes
redis-cli CONFIG GET active-defrag-*
# watch progress
redis-cli INFO memory | grep -E 'allocator_frag_ratio|active_defrag_running'
redis-cli INFO stats | grep -E 'active_defrag_(hits|misses|key_hits|key_misses)'
The defaults in Redis 7.4 and 8.0 and in Valkey 8.1 are the same:
active-defrag-ignore-bytes 100mb: do not start below 100 MB of fragmentation waste;active-defrag-threshold-lower 10: start at 10% fragmentation;active-defrag-threshold-upper 100: use maximum effort at 100%;active-defrag-cycle-min 1andactive-defrag-cycle-max 25: minimum and maximum effort, in percent of CPU;active-defrag-max-scan-fields 1000: how many set, hash, sorted set or list fields are processed from the main dictionary scan.
Valkey 8.1 adds active-defrag-cycle-us (default 500 microseconds), the length of each defrag cycle, which controls the latency impact on client commands. The config files themselves advise leaving the defaults alone unless you know what you are changing, and enabling the feature only when you actually have fragmentation.
There is also MEMORY PURGE, which asks jemalloc to release dirty pages. It can lower allocator_rss_bytes, but it does not compact anything, so it does not fix real fragmentation.
Large instances: what changes
- Defrag costs latency. Defragmentation does its work in the main thread in short cycles, and with the default maximum of 25% CPU it competes with client commands. On a busy 50 GB instance, watch command latency while it runs and lower
active-defrag-cycle-maxif p99 suffers. Enable it in a quieter period first. - It takes time. The process walks the keyspace incrementally. On tens of gigabytes, expect it to run for a long time rather than minutes; follow
active_defrag_hitsandallocator_frag_ratioinstead of waiting for a sudden drop. - Restart is the other fix, but not a free one. The config file notes that without active defrag a restart, or flushing and reloading the data, is normally needed to lower fragmentation. On a large instance that means minutes of loading from RDB or AOF. The safer path is a planned failover: let a replica take over and restart the old primary. A replica that has just loaded a full copy of the data usually starts with little fragmentation.
- Fork and copy-on-write. RDB snapshots and AOF rewrites fork the process. During the fork, pages that are written to are copied, so RSS can grow well beyond
used_memoryfor a while. With transparent huge pages enabled, the latency guide warns that this can turn into copying almost the whole process memory, with big latency and memory usage; it tells you to disable THP. We covered fork costs in Redis persistence: RDB vs AOF. - Headroom. Size the host for peak data, plus fragmentation, plus copy-on-write during persistence, plus replication and client buffers. The right margin depends on the write rate during persistence: measure RSS during a BGSAVE under real load.
Our rule of thumb: alert on mem_fragmentation_bytes and on a ratio below 1, not only on a ratio above 1.5. Before enabling defrag, check allocator_frag_ratio. If it is close to 1, the "fragmentation" is a past peak or overhead, and defrag will not help.
Checklist when the alert fires
- Check
mem_fragmentation_bytes: is the waste large enough to matter? - If the ratio is below 1, check swap in
/proc/<pid>/smapsandvmstatfirst. - Compare
used_memory_rsswithused_memory_peakto see whether you are looking at a past peak. - Read
allocator_frag_ratioandallocator_frag_bytesfor true fragmentation. - Confirm
mem_allocatoris jemalloc, then enableactivedefragin a quiet period and watch latency. - If defrag is not enough or not available, plan a failover to a replica and restart the old primary.
- Revisit sizing: peak data plus fragmentation plus fork overhead.
Key takeaways
mem_fragmentation_ratiois RSS divided byused_memoryand includes more than fragmentation.- The true fragmentation metric is
allocator_frag_ratio; small absolute waste is not a problem even at high ratios. - A high ratio after mass deletes is often a past peak. The memory is reused when data grows again.
- A ratio below 1 points to swapping and serious latency.
- Active defrag is off by default, needs jemalloc, and costs main-thread CPU on large instances.
- Size for peak plus fragmentation plus copy-on-write, not for
maxmemoryalone.