Memcached’s slab allocator is a surprisingly efficient, yet often misunderstood, approach to managing memory for a key-value store.

Let’s watch it in action. Imagine we have a Memcached instance and we want to store items of varying sizes.

# Start Memcached with a specific memory limit and chunk size
memcached -m 64 -o slab_reassign=1

Now, let’s add some data.

# Add a small item
echo "set key1 0 60 5\r\nvalue\r\n" | nc localhost 11211

# Add a larger item
echo "set key2 0 60 20\r\nthis is a larger value\r\n" | nc localhost 11211

Memcached doesn’t just grab raw memory and dole it out. It pre-allocates memory into fixed-size chunks called "slabs." These slabs are grouped by size. When you set an item, Memcached finds the smallest slab size that can accommodate your item’s data and associated overhead, then carves out a chunk from that slab.

Internally, Memcached starts with a default set of slab classes. These classes are essentially buckets for items of similar size. The allocator dynamically creates more slab classes as needed, up to a configurable limit. Each slab class has a growth factor that determines the size of the next larger slab class. For example, if a slab class is 64 bytes, the next might be 96 bytes (a 1.5x growth). This ensures that items don’t waste too much space by being in a slab that’s much larger than necessary.

The primary problem the slab allocator solves is fragmentation. In traditional memory allocators, as you allocate and free memory, small, unusable gaps can form between allocated blocks. This is external fragmentation. The slab allocator mitigates this by allocating memory in fixed-size chunks. An item is placed in the smallest available chunk that fits. Even if there’s a tiny bit of unused space within that chunk, it’s still considered "used" for that item, preventing it from becoming a fragmented gap. Internal fragmentation (unused space within an allocated chunk) is still a factor, but the trade-off is significantly reduced external fragmentation and faster allocation/deallocation.

The slab_reassign option, which we enabled, allows Memcached to dynamically move items between slabs if it detects that a slab class is underutilized and another is oversubscribed. This helps to keep memory usage balanced.

You control the memory management behavior primarily through the -m flag (total memory in MB) and the -o slab_reassign flag. The default growth factor for slab sizes is 1.25, meaning each subsequent slab class is 25% larger than the previous one. You can also influence the initial set of slab classes via the memcached-tool utility, though this is less common for day-to-day operations.

When an item is requested, Memcached first determines which slab class it would belong to based on its size. It then checks the LRU (Least Recently Used) list for that specific slab class to find an available chunk. If no chunks are available in that slab, and the slab_reassign option is enabled, Memcached might attempt to free up space in that slab by evicting older items or rebalancing from other slabs.

The most surprising aspect of the slab allocator is how it handles the "size" of an item for allocation purposes. It’s not just the raw data size. Memcached adds a fixed overhead to each item, which includes the key, value length, flags, expiration time, and pointers for the linked list. Therefore, an item that seems small based on its data alone might be allocated into a larger slab class due to this overhead.

The next concept you’ll likely grapple with is how Memcached’s eviction policies interact with the slab allocator, especially under memory pressure.

Want structured learning?

Take the full Memcached course →