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 of the chunk: 20% at , about 7% at . 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
redisObjectheader: 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
embstrencoding, 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.
| Class | Chunk | Per page | Pages | Items | Evictions |
|---|---|---|---|---|---|
| 1 | 96 B | 10,922 | 0 | 0 | 0 |
| 2 | 120 B | 8,738 | 0 | 0 | 0 |
| 3 | 152 B | 6,898 | 0 | 0 | 0 |
| 4 | 192 B | 5,461 | 0 | 0 | 0 |
| 5 | 240 B | 4,369 | 0 | 0 | 0 |
| 6 | 304 B | 3,449 | 0 | 0 | 0 |
| 7 | 384 B | 2,730 | 0 | 0 | 0 |
| 8 | 480 B | 2,184 | 0 | 0 | 0 |
| 9 | 600 B | 1,747 | 0 | 0 | 0 |
| 10 | 752 B | 1,394 | 0 | 0 | 0 |
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 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 automovemoves 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
- System Design Primer: Design a key-value cache, cache implementation
- Memcached: Performance (memory allocation for items)
- Memcached source: slabs.c and memcached.c (slab classes and defaults)
- Memcached protocol: slabs reassign and slabs automove
- Redis source: redisObject and embstr (7.4)
Adapted from The System Design Primer by Donne Martin, licensed under CC BY 4.0. Changes were made.