Use Redis Hashes for Objects
Learn to store and retrieve objects efficiently using Redis hashes: a hands-on guide to mapping real-world data structures to hash fields.
Focus: use redis hashes for objects
Storing flat key-value pairs works well for simple data, but real-world applications deal with objects: user profiles, product details, session data. Storing each field as a separate key clutters your namespace and hurts performance. Redis Hashes give you a compact, mutable way to represent objects as a single key with multiple fields — the map data structure you've been missing.
The problem this lesson solves
Imagine building a user profile service. Each user has a name, email, age, and preferences. With plain string keys, you'd end up with keys like user:1001:name, user:1001:email, and so on. This approach is wasteful: Redis stores each key with its own metadata overhead, and fetching or updating a full profile requires multiple round trips. Worse still, atomic updates to multiple fields are impossible without transactions or Lua scripts.
When you need to represent a mutable object with many fields, using a Redis Hash is the idiomatic solution. Hashes store field-value pairs under one key, provide O(1) operations for field-level access, and support partial updates and deletions — perfect for mapping programming objects to Redis.
Core concept / mental model
Think of a Redis Hash as a mini dictionary nested inside a Redis key. The outer key acts like a class name or an object ID, and the fields are its attributes. Just like a Python dict or a JavaScript object, you can set, get, increment, or delete individual fields without touching the rest.
Key properties of hashes
- Flat structure: fields are strings, values are strings (binary safe); no nesting
- Field-level operations:
HSET,HGET,HDEL,HINCRBY— you control each attribute independently - Bulk read/write:
HGETALL,HMGET,HMSETlet you fetch or set multiple fields in one command - Memory efficient: hashes are optimized when the number of fields is small (< 512 by default via
hash-max-ziplist-entries)
Pro tip: Use hashes when you need to update a subset of an object's properties atomically without a transaction. For example, incrementing a user's login count (
HINCRBY) while leaving other fields untouched.
How it works step by step
Here's the logical flow when working with Redis hashes for objects:
- Define the object's key pattern: choose a consistent naming convention like
object_type:id(e.g.,user:42,product:shirt-123). - Store the object: use
HSETwith the key, followed by alternating field-value pairs. If the key doesn't exist, Redis creates it automatically. - Read individual fields: use
HGETwith the key and field name. - Read the full object: use
HGETALL— returns all field-value pairs as a flat array (Redis calls it a "list of tuples" on the wire). - Update selective fields: use
HSETagain (it upserts — no error if the field doesn't exist). - Remove fields: use
HDELwith one or more field names. - Delete the hash entirely: use
DELon the key.
Key pattern best practices
- Always namespace with a colon:
object:type:id - Avoid spaces or special characters in field names
- Keep field names short to save memory
Hands-on walkthrough
Let's model a user object in Redis using the redis Python client. Make sure you have the package installed: pip install redis.
Example 1: Creating and reading a user profile
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Store a user object as a hash
user_key = "user:42"
r.hset(user_key, mapping={
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"city": "New York"
})
# Read single field
print(r.hget(user_key, "name")) # Output: Alice
# Read all fields
profile = r.hgetall(user_key)
print(profile)
# Output: {'name': 'Alice', 'email': 'alice@example.com', 'age': '30', 'city': 'New York'}
Example 2: Updating selective fields and increments
# Update email and increment age in one call (two separate commands)
r.hset(user_key, "email", "alice@newdomain.com")
r.hincrby(user_key, "age", 1)
# Check the result
updated = r.hgetall(user_key)
print(updated)
# Output: {'name': 'Alice', 'email': 'alice@newdomain.com', 'age': '31', 'city': 'New York'}
Example 3: Bulk reading with HMGET
# Get only name and email
fields = r.hmget(user_key, "name", "email")
print(fields) # Output: ['Alice', 'alice@newdomain.com']
Example 4: Removing a field and deleting the hash
# Remove city field
r.hdel(user_key, "city")
print(r.hgetall(user_key))
# Output: {'name': 'Alice', 'email': 'alice@newdomain.com', 'age': '31'}
# Delete the entire hash
r.delete(user_key)
print(r.exists(user_key)) # Output: 0
Pro tip: Use
hscan_iter()when dealing with hashes that have many fields — it avoids blocking the server by returning results in batches (cursor-based).
Compare options / when to choose what
| Data Type | Best for | Drawbacks for objects |
|---|---|---|
| String (JSON blob) | Simple, nested objects | Cannot update individual fields without re-serializing entire blob; not atomic for partial updates |
| Hash (this lesson) | Flat objects with known fields, frequent partial updates | Cannot represent nested structures (no sub-objects) |
| JSON module (RedisJSON) | Deeply nested objects, querying by fields | Requires RedisJSON module; heavier operations |
| Set / Sorted Set | Collections, relationships | Not suitable for key-value attributes |
For most CRUD-oriented objects (user profiles, product details, configuration), hashes strike the perfect balance of simplicity, performance, and atomicity.
Troubleshooting & edge cases
1. HGETALL returns an empty dictionary unexpectedly
- Cause: The hash key exists but has no fields. Check with
HLEN. - Fix: Re-initialize the fields with
HSET.
2. Fields stored as bytes in Python
- Solution: When not using
decode_responses=True, fields arebytes. Decode manually:value = r.hget(key, field).decode('utf-8').
3. Hash key conflicts with another type
- Symptom:
WRONGTYPE Operation against a key holding the wrong kind of value - Fix: Use
TYPE keyto inspect, thenDELif safe, or rename keys to follow a consistent namespace.
4. Memory warnings due to huge hashes
- Scenario: Hash with >10,000 fields causes slow
HGETALL. - Mitigation: Use
HSCANorHGETALLonly when necessary. Consider splitting into multiple hashes (e.g.,user:42:meta,user:42:preferences).
5. Integer overflow with HINCRBY
- Note:
HINCRBYworks on 64-bit signed integers. Exceeding ±9 quintillion raises an error. UseHINCRBYFLOATfor floating-point counters.
What you learned & what's next
You now know how to use Redis hashes for objects: create them with HSET, read with HGET/HGETALL, update partially with HSET/HINCRBY, and delete fields with HDEL. You understand the mental model of hashes as flat dictionaries and can choose hashes over strings or JSON modules based on your data shape.
What's next? The next lesson explores expiring hashes with TTL — controlling object lifetime for sessions and caches. You'll combine hashes with expiration policies to build self-cleaning data stores.
Practice recap
Try it yourself: Write a Python script that stores a book object with fields title, author, year, and isbn. Then update the year field, read the full object, and finally delete the isbn field. Print the hash after each step to verify your commands.
Common mistakes
- Storing each object field as a separate Redis string key (e.g.,
user:42:name,user:42:email) — this wastes memory and makes atomic updates impossible. - Using
HSETwithoutmappingin older Redis clients, leading to extra round trips when setting multiple fields. - Forgetting that
HGETALLreturns a dictionary with all fields — avoid it for very large hashes; useHSCANinstead. - Assuming hashes support nested objects — they don't. For nested data, serialize to JSON or use the RedisJSON module.
Variations
- Use
HSETNXto set a field only if it doesn't already exist (atomicset-if-not-existsper field). - For large hashes, use
HSCANwith a cursor to iterate fields in batches instead ofHGETALL. - Combine hashes with
EXPIREfor time-limited objects (e.g., user sessions).
Real-world use cases
- Store user session data (last login, preferences) – fast field updates without fetching entire session.
- Cache product catalog items – each product is a hash with fields like price, stock, description.
- Manage configuration objects (e.g., feature flags) – update a single flag atomically across all instances.
Key takeaways
- Redis hashes store field-value pairs under one key — perfect for mapping programming objects to Redis.
- Use
HSETto create/update,HGETto read one field,HGETALLto read all fields of a hash. - Hashes support partial updates (HSET, HDEL, HINCRBY) without touching other fields.
- Choose hashes over plain strings when you need atomic updates or partial modifications of an object.
- Avoid huge hashes with thousands of fields — use HSCAN for iteration and consider sharding.
- Always namespace hash keys consistently (e.g.,
user:42) to avoid collisions.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.