fivenines

Theory / 10 min

Redis Object Model

Redis keys do not point directly to raw values. They point to objects that know what kind of value they hold and how that value is represented internally.

That extra layer is one of the most important ideas in Redis internals:

key -> Redis object -> type + encoding + value + metadata

The logical type tells commands what behavior is valid. The encoding tells the server how the value is physically stored.

Type And Encoding Are Different Ideas

A Redis value can have a logical type such as string, list, hash, set, sorted set, or stream. But the internal encoding may vary depending on the size and shape of the value.

Examples:

string -> compact integer representation for small integer-like values
string -> embedded or raw byte representation for other strings
hash   -> compact listpack when small
hash   -> hash table when larger
set    -> integer set when all members are small integers
set    -> hash table otherwise
zset   -> compact listpack when small
zset   -> skiplist plus dictionary when larger

The exact encodings change across Redis versions, but the principle is stable: commands operate on logical types, while the server is free to optimize physical representation.

Why This Matters

A hash with two fields should not pay the same overhead as a hash with two million fields. A set of small integers can be stored more compactly than a general set of arbitrary byte strings. A sorted set needs efficient lookup by member and efficient traversal by score, which eventually leads to a hybrid representation.

Without an object model, each command would need to know too much about storage. With an object model, a command can ask for "the hash stored at this key" and then operate through type-specific helpers that understand encodings.

Wrong Type Is A Feature

Redis does not silently reinterpret values. If name is a string, then LPUSH name Ada returns a wrong-type error. That protects the keyspace from accidental shape changes.

The common lookup pattern is:

function lookupKeyAs(db, key, expectedType) {
  const object = db.get(key);
  if (!object) return null;
  if (object.type !== expectedType) throw wrongType();
  return object;
}

Commands usually follow the same rhythm:

lookup key
check type
create empty object if the command allows it
perform operation
delete key if the value becomes empty and Redis semantics require it

That rhythm keeps type behavior predictable across the command surface.

Metadata Connects Objects To Global Policy

Redis objects also carry or connect to metadata used by wider systems. Eviction policies need recency or frequency signals. Memory accounting needs to estimate cost. Persistence needs type information. Replication and propagation need to understand which commands changed which objects. Debugging and introspection need visibility into encoding.

A simplified object might look like:

type RedisObject = {
  type: "string" | "list" | "hash" | "set" | "zset" | "stream";
  encoding: string;
  value: unknown;
  lruOrLfu: number;
  refcount: number;
};

Not every implementation needs every field, and production Redis has its own highly tuned structures. The architectural lesson is that values need identity beyond their raw payload.

The Object Model Lets Redis Stay Small And Fast

Redis feels simple at the command level because complexity has been pushed behind well-chosen boundaries. Users see HSET, HGET, and HGETALL. Internally, Redis can store a tiny hash compactly, upgrade it when it grows, track it for eviction, serialize it for persistence, and reject commands that expect another type.

That is the object model doing quiet work. It lets Redis combine a clean command interface with storage decisions that would be impossible if every key were just a pointer to untyped data.

Next step

See what actually stuck.

Take the practice scenarios now.