FjellSystem Design Lab
Lesson 14.2• 10 min read

Memory: slab allocation, calcification and jemalloc

LRU eviction, Memcached’s 1 MB pages and slab classes, internal waste, calcification and slab rebalancing, and Redis object memory.

What you will learn

  • Work out Memcached's slab classes from the growth factor.
  • Calculate the space a chunk wastes, and recognize slab calcification.
  • Explain how Redis allocates memory instead.

What to evict

The primer uses least recently used (LRU) eviction: new and recently used entries go to the head of a doubly linked list, and the entry at the tail is evicted when the cache is full. Chapter 6 compares LRU with LFU and ARC, and Redis's sampled approximations of them.


Memcached's slab allocator

Memcached divides memory into pages, 1 MB by default. Each page is assigned to a slab class and cut into equal chunks. In the documentation's words, items are "pushed into the slab class of the nearest fit".

How the classes are sized (from slabs.c): the smallest chunk is the item header plus chunk_size (48 + 48 = 96 bytes on 64-bit builds). Each next class multiplies by the growth factor (-f, default 1.25) and rounds up to a multiple of 8:

Class Chunk Chunks per 1 MB page
1 96 B 10,922
2 120 B 8,738
3 152 B 6,898
4 192 B 5,461
5 240 B 4,369

A chunk holds the whole item: a 48-byte header, 8 bytes of CAS value, the key, the value and terminators, not just the key or value.

Internal waste. An item uses the smallest chunk that fits, and the rest of the chunk is unused. The worst case is an item just over the previous class's size, wasting about 1−1/f1 - 1/f of the chunk: 20% at f=1.25f = 1.25, about 7% at f=1.08f = 1.08. A smaller factor wastes less per item but creates more classes, which spreads pages across more of them.

No holes between items. Chunks in a page are all one size, so a freed chunk is exactly right for the next item of that class.


Slab calcification

Pages are assigned to classes as traffic arrives. If the workload changes, say from many small items to larger ones, the pages that went to the small class stay there even when they are empty. The large class has no free pages, so it evicts recent items while memory sits idle elsewhere.

Memcached's fix is to move pages between classes. The slabs reassign command moves a page from one class to another, and slabs automove runs a background thread that "decides on its own when to move memory between slab classes". In current Memcached source both are on by default (slab_reassign = true, slab_automove = 1). The visual lets you calcify a tiny cache and then run the rebalancer.


Redis's memory

Redis does not use slab pages. It allocates each object through its allocator, by default jemalloc on Linux (chapter 6):

  • Every value is wrapped in a 16-byte redisObject header: 4 bits of type, 4 bits of encoding, 24 bits of LRU or LFU data, a 32-bit reference count and a 64-bit pointer.
  • Strings of up to 44 bytes use the embstr encoding, which allocates the header and the string together: one allocation instead of two.
  • Active defragmentation exists but is off by default (chapter 6).

Memcached slab classes

Classes are computed as Memcached does: start at 96 bytes, multiply by the growth factor, align to 8. Each class takes 1 MB pages from a shared pool and cuts them into equal chunks.

Growth factor (-f)
Item ≈ 119 B (48 B header, 8 B CAS, key, value, terminators) → class 2 (120 B chunk), 0.8% of the chunk unused. Worst case for factor 1.25: 20.0%.
Free pages in pool: 10 of 10
ClassChunkPer pagePagesItemsEvictions
196 B10,922000
2120 B8,738000
3152 B6,898000
4192 B5,461000
5240 B4,369000
6304 B3,449000
7384 B2,730000
8480 B2,184000
9600 B1,747000
10752 B1,394000
10 pages of 1 MB, none assigned yet.

Key takeaways

  • Memcached cuts 1 MB pages into equal chunks per class, starting at 96 bytes and growing by the factor (1.25 by default); a chunk holds the whole item, header included.
  • An item uses the smallest chunk it fits, wasting up to about 1−1/f1 - 1/f of it, but chunks of one size leave no holes between items.
  • Pages stay with the class they were first given, so a changed workload starves the new classes; slabs automove moves pages between classes.
  • Redis allocates each object through jemalloc with a 16-byte header, and stores strings up to 44 bytes in one allocation (embstr).

Sources

Adapted from The System Design Primer by Donne Martin, licensed under CC BY 4.0. Changes were made.