REDIS INTERNALS Chapter 5 · Objects & Memory Contents Cheatsheet

Chapter 5 of 12 · Core objects and memory

Collections in Disguise: Lists, Hashes, Sets, Sorted Sets

Build a tiny hash, then keep growing it, and watch its encoding change underneath you without a single command changing shape:

Terminal — a hash that outgrows its compact encoding$ redis-cli HSET user:1 name "Ava" age 29
(integer) 2
$ redis-cli OBJECT ENCODING user:1
"listpack"
$ # now push past hash-max-listpack-entries (default 128)
$ for i in $(seq 1 200); do redis-cli HSET user:1 f$i v$i > /dev/null; done
$ redis-cli OBJECT ENCODING user:1
"hashtable"

HSET, HGET, HGETALL all still work exactly the same. What changed is invisible from the API and significant in memory: a small hash is packed tight; a big one gets a real hash table underneath. Every collection type makes this same trade, with its own thresholds and its own compact format.

The Compact Formats: listpack and intset

For small collections, Redis avoids per-element pointers entirely and instead packs every entry — length-prefixed, back-to-back — into one contiguous block of memory:

A contiguous, pointer-free block is small and cache-friendly to scan linearly — but it's still O(n) to scan. That's fine as long as "n" stays small; the whole strategy depends on collections usually being small enough that the difference between O(1) hashing and O(n) linear scan doesn't matter, while the memory savings always do.

When a Collection Grows: quicklist and skiplist

Past a size or element-length threshold, each type converts to a structure built for scale instead of density:

TypeCompact (small)Scaled (large)Config knob
Listlistpackquicklistlist-max-listpack-size
Hashlistpackhashtablehash-max-listpack-entries / -value
Set (ints)intsetlistpack / hashtableset-max-intset-entries
Set (strings)listpackhashtableset-max-listpack-entries
Sorted setlistpackskiplistzset-max-listpack-entries / -value

A quicklist is a doubly linked list of nodes, where each node is itself a small listpack — the best of both shapes: cheap inserts/removals at either end like a linked list, but each node still packs several elements densely instead of one pointer-heavy node per element. A skiplist is the classic probabilistic structure with multiple forward-pointer "levels" per node, giving sorted sets O(log n) insert, remove, and range queries by score — something no flat array could do efficiently once the set gets large. Redis pairs the skiplist with a plain dictionary for O(1) score lookups by member, so a big sorted set actually keeps two structures over the same data, each optimized for a different access pattern.

A one-way ratchet: a small collection starts in a compact listpack or intset; once it crosses a configured size or value-length threshold it converts to a scaled structure like quicklist, hashtable, or skiplist, and never converts back. listpack / intset flat, packed, cache-friendly good while n is small crosses threshold — one-way quicklist / hashtable / skiplist built for scale, not density
The ratchet only turns one way. Once a collection converts to its scaled encoding, deleting elements back down below the threshold does not convert it back — converting is a one-time cost Redis is willing to pay to grow, not something worth re-paying on every shrink.
Key Idea

Every collection type separates its abstract API (push, pop, add, range, score) from its physical encoding, and every one of them defaults to the cheapest encoding that fits the data, converting up as it grows. This is the same move Chapter 4 made for strings, applied independently, type by type, all the way through the codebase — a pattern worth noticing now because Chapter 12 will ask you to go looking for it elsewhere too.

Warning

Conversion is triggered by either the element count or the size of any individual value — a hash with only three fields still becomes a hashtable if one of those values is a 10 KB blob, because hash-max-listpack-value caps individual entry size, not just entry count. If your memory usage jumps unexpectedly on a collection you assumed was "small," check the value sizes, not just the length.

Try It Yourself

Compare the two set encodings directly: SADD ints 1 2 3 then OBJECT ENCODING ints (expect intset), versus SADD words a b c then OBJECT ENCODING words (expect listpack, since strings can't use the integer-only intset). Then run SADD ints hello — adding one non-integer member to an intset — and check the encoding again. One non-numeric member is enough to convert the whole set away from intset.

Encodings solve memory density for individual values and small collections. But the keyspace itself — the mapping from every key name to its value — is one giant hash table, and it has its own much more interesting scaling story: how do you resize a hash table holding millions of entries without ever pausing to do it all at once? That's next.

Further Reading antirez/listpack — format spec · redis.io — OBJECT ENCODING

⌂ Library