Encodings flip under you

Each Redis type has multiple internal encodings chosen by size, and crossing a threshold silently rewrites memory layout and operation cost.

Previously

One loop, one command at a time. So the cost of a single command — and therefore everyone else's wait — depends on the SHAPE of the value the loop is touching.

Scene 03

Encodings flip under you

  1. Watch
  2. Try it
  3. Predict
  4. Capture
myhashHASHlistpackone allocation · packedMemory144 Bbytes used by this keyOp-costper lookupO(N)linear scan · grows with NVALUE BODY · contiguous block4 entriesnamealiceemaila@x.iotiergoldcountryNL← one contiguous block →hash-max-listpack-entriescurrent 4 / limit 80threshold = 84
Listpack: one contiguous allocation, O(N) per op but cache-friendly small.
What to watch for

Watch a hash grow one field at a time. It starts as a single contiguous listpack — one allocation, ideal cache locality. When the entry count crosses the threshold, the layout morphs into a chained hashtable.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Hash.hset
every HSET checks the threshold before appending
def hset(key, field, value):
h = lookupOrCreateHash(key)
if h.encoding == 'listpack':
# both knobs gate the encoding; either trips the flip
if (h.entryCount + 1 > hash_max_listpack_entries
or len(value) > hash_max_listpack_value):
tryConvertEncoding(h) # listpack -> hashtable
if h.encoding == 'listpack':
listpackUpsert(h.lp, field, value)
else:
dictUpsert(h.ht, field, value)
h.entryCount += 1
tryConvertEncoding (listpack -> hashtable)
rehash every entry into a dict; the listpack is freed
def tryConvertEncoding(h):
ht = dictCreate()
# walk the contiguous listpack cell by cell
for field, value in listpackIter(h.lp):
dictAdd(ht, field, value)
listpackFree(h.lp)
h.lp = None
h.ht = ht
h.encoding = 'hashtable' # one-way; never reset on HDEL
Hash.hget
O(N) listpack scan vs O(1) hashtable lookup
def hget(key, field):
h = lookupHash(key)
if h is None: return None
if h.encoding == 'listpack':
# contiguous strip, no index — walk every cell
for f, v in listpackIter(h.lp):
if f == field: return v
return None
else:
# bucket lookup, pointer chase, ~3x memory
return dictFetch(h.ht, field)

Where this sits in Build Redis

Scene 03 of 10, in the The loop act — One thread, one command — and the encoding under it.. Listpack ↔ hashtable, intset ↔ hashtable, embstr ↔ raw — crossing a threshold silently rewrites memory and op-cost.

Up next. Persistence — those carefully encoded bytes only live in RAM. Persistence is how Redis turns RAM into something that can survive a crash.

All 10 scenes in Build Redis · Every curriculum

Built with Arqly
Every scene in Build Redis builds on the one before it.All 10 Build Redis scenes