Redis Hash for Object Modeling: When and How to Use HSET

Redis Hashes are the natural choice for storing objects — users, products, sessions, configurations. But there are subtle decisions that affect memory usage, query patterns, and maintainability. This guide covers the right way to model data with Hashes.

The Problem

You need to store a user profile. Option A: serialize the whole object as a JSON string. Option B: use a Hash with individual fields.

SET user:1001 '{"name":"Alice","age":30,"email":"alice@example.com","role":"admin"}'

HSET user:1001 name "Alice" age "30" email "alice@example.com" role "admin"

Both work. But they have very different characteristics.

Hash vs String (JSON): When to Use Which

Use Hash When:

  • You need to read/update individual fields without fetching the entire object.
  • The object has a flat structure (no nesting).
  • You want memory efficiency for many small objects (ziplist encoding).

Use String (JSON) When:

  • The object is deeply nested.
  • You always read/write the entire object.
  • You need complex querying (consider RedisJSON module).

Basic HSET Patterns

Creating an Object

HSET user:1001 name "Alice" age "30" email "alice@example.com" role "admin" created_at "2026-01-15"
HGETALL user:1001

HSET supports setting multiple fields in a single command (Redis 4.0+).

Reading Fields

HGET user:1001 name
HGET user:1001 email

HMGET user:1001 name email role

HMGET fetches multiple fields in one round trip — much better than multiple HGET calls.

Partial Updates

This is where Hashes shine. Update one field without touching others:

HSET user:1001 role "superadmin"
HSET user:1001 last_login "2026-02-15"
HGETALL user:1001

With JSON strings, you'd need to GET, deserialize, modify, serialize, and SET — four operations instead of one.

Atomic Counters in Hashes

HSET product:2001 name "Widget" price "9.99" views "0" stock "100"
HINCRBY product:2001 views 1
HINCRBY product:2001 views 1
HINCRBY product:2001 stock -1
HGETALL product:2001

HINCRBY atomically increments a numeric field. Great for counters, stock levels, and view counts.

Float Increments

HSET account:1001 balance "100.00"
HINCRBYFLOAT account:1001 balance 25.50
HINCRBYFLOAT account:1001 balance -10.00
HGET account:1001 balance

Checking Field Existence

HEXISTS user:1001 email
HEXISTS user:1001 phone

Useful for conditional logic — e.g., only send verification email if the email field exists.

Deleting Fields

HDEL user:1001 created_at
HGETALL user:1001
HLEN user:1001

HDEL removes specific fields. HLEN returns the number of remaining fields.

Memory Optimization: Ziplist Encoding

Redis uses a compact "ziplist" encoding for small Hashes. This is significantly more memory-efficient than the hashtable encoding used for large Hashes.

Default thresholds:

  • hash-max-ziplist-entries: 128 (max number of fields)
  • hash-max-ziplist-value: 64 (max bytes per value)

If your Hash stays within these limits, Redis stores it as a ziplist — using roughly 10x less memory.

HSET small:hash f1 "v1" f2 "v2" f3 "v3"
OBJECT ENCODING small:hash

You should see ziplist (or listpack in Redis 7+).

Pitfall

If any single field value exceeds 64 bytes, the entire Hash converts to hashtable encoding. Keep values short.

HSET test:encoding short "hello"
OBJECT ENCODING test:encoding
HSET test:encoding long "this is a very long string that exceeds the sixty four byte limit for ziplist encoding in redis"
OBJECT ENCODING test:encoding

Modeling Patterns

User Profile

HSET user:1001 username "alice" display_name "Alice Chen" email "alice@example.com" role "admin" status "active" login_count "0"
HINCRBY user:1001 login_count 1
HMGET user:1001 username display_name role

Shopping Cart

HSET cart:user:1001 "product:2001" "2" "product:2002" "1" "product:2003" "3"
HINCRBY cart:user:1001 "product:2001" 1
HDEL cart:user:1001 "product:2003"
HGETALL cart:user:1001

Field = product ID, value = quantity. Atomic increment for "add to cart."

Configuration Store

HSET config:app max_upload_size "10485760" maintenance_mode "false" api_rate_limit "100" feature_dark_mode "true"
HGET config:app maintenance_mode
HSET config:app maintenance_mode "true"

Session Data

HSET session:abc123 user_id "1001" ip "192.168.1.1" user_agent "Mozilla/5.0" created_at "1739600000"
EXPIRE session:abc123 3600
HGET session:abc123 user_id
TTL session:abc123

Handling Nested Data

Hashes are flat — no nesting. For nested objects, you have three options:

Option 1: Flatten with Dot Notation

HSET user:1001 "address.street" "123 Main St" "address.city" "Springfield" "address.zip" "62701"
HGET user:1001 "address.city"

Option 2: JSON in a Field

HSET user:1001 name "Alice" address '{"street":"123 Main St","city":"Springfield","zip":"62701"}'
HGET user:1001 address

Option 3: Separate Hash

HSET user:1001 name "Alice" address_key "user:1001:address"
HSET user:1001:address street "123 Main St" city "Springfield" zip "62701"
HGETALL user:1001:address

Pitfalls

  1. No per-field TTL: You can't expire individual fields. TTL applies to the entire key. If you need per-field expiration, use separate keys.
  2. No atomic multi-key Hash operations: HMGET works within one Hash. To read fields from multiple Hashes, use MULTI/EXEC or pipeline.
  3. Large Hashes block: HGETALL on a Hash with 100K fields blocks Redis. Use HSCAN for large Hashes:
HSCAN user:1001 0 COUNT 10
  1. Type confusion: All Hash values are strings. HGET returns "30", not the integer 30. Parse in your application.
  2. Overusing Hashes: Don't use a single Hash as a "database table" with millions of fields. Use separate keys with a naming convention.

Try It in the Editor

Head to the Redis Online Editor and model some objects:

HSET user:1001 name "Alice" age "30" email "alice@example.com" role "admin"
HGETALL user:1001
HMGET user:1001 name role
HSET user:1001 role "superadmin" last_login "2026-02-15"
HINCRBY user:1001 age 1
HGETALL user:1001

HSET cart:user:1001 "product:2001" "2" "product:2002" "1"
HINCRBY cart:user:1001 "product:2001" 1
HGETALL cart:user:1001

Notice how partial updates and atomic increments work seamlessly. That's the power of Hash-based object modeling.