Chapter 18 · Memory Management, Eviction, Expiration, Fragmentation, and OOM Prevention

used_memory vs RSS, Allocator Overhead, Fragmentation, Buffers, and Replication/AOF Memory

Separate Redis logical allocation from process RSS and allocator fragmentation, then account for client, AOF, replication, and other memory that can sit outside the eviction budget.

Advanced180–250 minutesused_memory, RSS, allocator, fragmentation, AOF/replication buffersRedis Open Source 8.10.1Docker + redis-py 8.1.0Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart sees a container at 70% of its memory limit while Redis reports a much smaller dataset. The discrepancy is not automatically a leak: Redis exposes several layers of memory that answer different questions. This lesson builds the vocabulary before any eviction policy is changed.

01

Distinguish used_memory, used_memory_dataset, allocator metrics, and process RSS.

02

Interpret fragmentation using allocator and RSS metrics rather than one ratio in isolation.

03

Identify client, AOF, replication, and other buffers that consume process memory.

04

Explain why maxmemory is an eviction/data budget rather than a hard RSS ceiling.

05

Measure allocation/freeing behavior safely on a dedicated node and preserve headroom for background work.

Exact Chapter 18 lab boundary

The course baseline remains Redis Open Source 8.10.1 from pinned image redis:8.10.1. Memory-pressure experiments use dedicated disposable Chapter 18 containers, named volumes, and loopback-only host ports 6391–6395 so the shared Chapter 01 lab is never pushed toward eviction or out-of-memory behavior. The temporary academy-admin password is classroom-only. TLS is omitted only on loopback. Mandatory topology is standalone, logical DB 0. Each lesson states its own persistence, maxmemory, eviction, and container-memory settings. Lesson 1 enables AOF everysec but leaves maxmemory at 0 so memory layers can be observed without eviction. The container is capped at 192 MiB only as a host-safety boundary, not as a recommended production ratio.

1. Four numbers, four different questions

used_memory is Redis allocator-accounted memory. used_memory_dataset estimates the dataset portion after Redis overhead. allocator_active includes active allocator pages and therefore external fragmentation. used_memory_rss is resident memory reported by the operating system for the Redis process. RSS can stay elevated after keys are deleted because the allocator may retain pages for reuse instead of immediately returning them to the OS.

Metric Question it answers Common mistake
used_memory How many bytes Redis currently accounts through its allocator? Calling this the whole process footprint.
used_memory_dataset How much allocator-accounted memory belongs to data rather than Redis overhead? Treating it as serialized value bytes only.
allocator_active / allocator_allocated How much external allocator fragmentation exists? Using mem_fragmentation_ratio alone.
used_memory_rss How many resident pages does the process occupy now? Expecting it to fall immediately after DEL/UNLINK.
Important ratio boundary

mem_fragmentation_ratio = used_memory_rss / used_memory includes allocator fragmentation plus other RSS overhead such as code, libraries, and stacks. Redis documents allocator_frag_ratio = allocator_active / allocator_allocated as the more direct external-fragmentation signal.

2. Start a bounded node and take a baseline

Shell · create one isolated memory node
docker volume create atlasmart-redis-ch18-memory_datadocker run --rm -v atlasmart-redis-ch18-memory_data:/data redis:8.10.1 sh -lc 'printf "%s\n" "user default off" "user academy-admin on >AtlasMart-Admin-Lab-Only-2026 ~* &* +@all" > /data/users.acl'docker run -d --name atlasmart-redis-ch18-memory --memory 192m -p 127.0.0.1:6391:6379 -v atlasmart-redis-ch18-memory_data:/data redis:8.10.1 redis-server --dir /data --aclfile /data/users.acl --appendonly yes --appendfsync everysec --save '' --maxmemory 0 --maxmemory-policy noevictiondocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin PINGdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin ACL WHOAMI
redis-cli · memory and persistence baseline
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO memorydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin MEMORY STATSdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin MEMORY DOCTORdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin CONFIG GET maxmemory maxmemory-policy appendonly appendfsync

Record the actual allocator name. MEMORY PURGE and allocator internals are jemalloc-specific; on another allocator, a purge can be a benign no-op.

3. Grow logical data and observe allocator/process growth

The payload is deterministic and bounded: 1,200 keys × 8 KiB is under 10 MiB of raw payload before key/object/allocator overhead. Do not infer an exact overhead percentage from one run; the encoding, allocator, architecture, and Redis version affect it.

Python · bounded allocation fixture
import os, redisHOST = "127.0.0.1"PORT = 6391r = redis.Redis(host=HOST, port=PORT, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=False)assert r.ping()payload = b"x" * 8192pipe = r.pipeline(transaction=False)for i in range(1200):    pipe.set(f"atlasmart:ch18:l1:blob:{i}", payload)pipe.execute()m = r.info("memory")for k in ("used_memory","used_memory_dataset","used_memory_rss","allocator_allocated","allocator_active","allocator_resident","mem_fragmentation_bytes","mem_not_counted_for_evict","mem_aof_buffer"):    print(k, m.get(k))
redis-cli · compare server views
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin DBSIZEdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin MEMORY USAGE atlasmart:ch18:l1:blob:0docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO memorydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin MEMORY STATS

4. Freeing logical data does not promise an immediate RSS drop

UNLINK removes a key from the keyspace immediately and lets background threads reclaim the value later. Even after Redis frees those allocations, jemalloc may keep pages resident for reuse. This is why “DBSIZE fell but RSS did not” is not sufficient evidence of a leak.

Python · unlink half of the bounded fixture
import os, redisHOST = "127.0.0.1"PORT = 6391r = redis.Redis(host=HOST, port=PORT, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=False)assert r.ping()keys = [f"atlasmart:ch18:l1:blob:{i}" for i in range(0, 1200, 2)]for start in range(0, len(keys), 100):    r.unlink(*keys[start:start+100])print("remaining", r.dbsize())print("memory", {k:r.info("memory").get(k) for k in ("used_memory","used_memory_rss","lazyfree_pending_objects","lazyfreed_objects")})
redis-cli · optional allocator observation
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO memorydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin MEMORY DOCTORdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin MEMORY PURGEdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO memory# MEMORY PURGE asks the allocator to return reclaimable pages; it is not a guarantee that RSS reaches used_memory.

5. maxmemory intentionally excludes some transient buffers

Redis documents mem_not_counted_for_evict as memory, principally transient replica and AOF buffers, that is not counted when deciding whether key eviction is required. The reason is to avoid an eviction feedback loop: evicting a key itself generates replication/AOF traffic. The consequence is operationally important—maxmemory cannot equal the container or host RAM limit.

Memory component Compared to maxmemory? Budget implication
Dataset and ordinary Redis allocations Generally yes This is the core eviction budget.
Transient replica/AOF buffers Excluded via mem_not_counted_for_evict Leave separate headroom.
Allocator/RSS slack Not a hard maxmemory-controlled ceiling Size the process/container for peak RSS, not just dataset.
Fork copy-on-write Temporary physical-memory pressure Reserve background persistence/full-sync headroom.
Search/vector/time-series indexes Consume Redis/process memory Measure their actual footprint and include it in the data/platform budget.
redis-cli · evidence fields
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO memorydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO persistencedocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-memory redis-cli --user academy-admin INFO replication

6. Redis 8.10 adds another memory dimension: compact-hash templates

Redis 8.10 adds compact hashes and reports used_memory_hash_templates in INFO MEMORY. MEMORY USAGE for a compact-hash key includes that key's proportional share of a shared template. This is a useful reminder that per-key memory accounting can include shared structures and that a memory model frozen on an older release can become incomplete.

Version-specific metric

Do not assume used_memory_hash_templates exists on every Redis 8.x release. It is an 8.10 metric.

7. Deliberately wrong model: “set maxmemory to container RAM”

Suppose AtlasMart allocates a 2 GiB container and sets maxmemory 2gb. That leaves no room for excluded buffers, RSS/allocator slack, persistence or replication COW, client buffers, executable/library pages, or other process overhead. The kernel/container runtime—not Redis eviction—may become the final limiter.

Repair

Start from the process/container envelope and subtract measured non-evictable and transient headroom before choosing maxmemory. Re-measure at peak write rate, during BGSAVE/AOF rewrite/full sync, and after failover topology changes.

8. Verification checklist

  • Capture used_memory, used_memory_dataset, used_memory_rss, allocator metrics, and mem_not_counted_for_evict together.
  • Verify RSS may remain above logical allocation after deletes without claiming a leak.
  • Confirm AOF/replication buffers are budgeted separately from maxmemory.
  • Record the allocator before relying on jemalloc-specific diagnostics.
  • Never infer a production headroom percentage from this tiny fixture.
Shell · bounded cleanup
docker rm -f atlasmart-redis-ch18-memorydocker volume rm atlasmart-redis-ch18-memory_data# Removes only this named Chapter 18 node and volume.

Check your understanding

  1. Why can RSS stay high after many keys are removed?
  2. Why is mem_not_counted_for_evict operationally important?
  3. Is mem_fragmentation_ratio a pure allocator-fragmentation metric?
  4. Does maxmemory guarantee the Redis process cannot exceed that many bytes?
Review the answers

Because allocator pages can remain resident and reusable even after Redis frees logical allocations; process RSS is not identical to current dataset bytes.

It represents memory such as transient AOF/replication buffers that is not compared with maxmemory, so the process can exceed the eviction budget.

No. It compares process RSS with used_memory and includes non-allocator RSS overhead; allocator_frag_ratio is the more direct external-fragmentation signal.

No. It is an eviction/data limit with excluded buffers and other process/RSS overhead outside it.

Production judgment and next bridge

Monitor memory as a vector of signals, not one percentage. Alert on available host/container memory, RSS, allocator fragmentation bytes, excluded buffers, client buffers, fork/COW peaks, and the workload-specific index footprint. Lesson 2 now turns that measured envelope into an explicit maxmemory and eviction-policy choice.

Summary and next step

used_memory vs RSS, Allocator Overhead, Fragmentation, Buffers, and Replication/AOF Memory is now connected to observable Redis behavior, bounded failure cases, and production tradeoffs. Keep the evidence and cleanup state from this lesson; next, continue with maxmemory and Eviction Policies: noeviction, LRU, LFU, Random, and TTL-Aware Choices.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.