Modeling Objects with Hashes
One hash per object — the canonical pattern
Section titled “One hash per object — the canonical pattern”The idiomatic way to model a domain object in Redis is to store all its fields under a single hash key. A user record, for example, lives entirely under user:1:
127.0.0.1:6379> HSET user:1 name "Ada" email "[email protected]" age "28" points "0"(integer) 4127.0.0.1:6379> HGETALL user:11) "name"2) "Ada"3) "email"4) "[email protected]"5) "age"6) "28"7) "points"8) "0"Compare that to the many-keys alternative:
| Approach | Keys required |
|---|---|
| Hash | user:1 — one key, one HSET |
| Separate strings | user:1:name, user:1:email, user:1:age, user:1:points — four keys |
The hash approach uses less memory and lets you fetch or update a specific field — with HGET / HSET — without loading or touching any other field in the object.
HSET user:1 name "Ada" email "[email protected]" age "28" points "0"
HGETALL user:1HINCRBY and HINCRBYFLOAT
Section titled “HINCRBY and HINCRBYFLOAT”When a hash field holds a number, Redis can increment it atomically — no read-modify-write race:
HINCRBY key field increment— increment an integer field by an integer amountHINCRBYFLOAT key field increment— increment a float field by a float amount
127.0.0.1:6379> HINCRBY user:1 points 10(integer) 10127.0.0.1:6379> HINCRBY user:1 age 1(integer) 29127.0.0.1:6379> HINCRBYFLOAT user:1 score 1.5"1.5"127.0.0.1:6379> HINCRBYFLOAT user:1 score 0.5"2"127.0.0.1:6379> HGET user:1 points"10"If the field does not exist yet, both commands treat its starting value as 0 before applying the increment, so HINCRBY user:1 points 5 on a missing points field creates it with the value 5.
HSET user:1 name "Ada" email "[email protected]" age "28" points "0"
HINCRBY user:1 points 10
HINCRBY user:1 age 1
HINCRBYFLOAT user:1 score 1.5
HINCRBYFLOAT user:1 score 0.5
HGET user:1 pointsHSETNX — conditional field set
Section titled “HSETNX — conditional field set”HSETNX key field value sets a field only if it does not already exist. It returns 1 if the field was created, 0 if it was already present and left unchanged.
127.0.0.1:6379> HSETNX user:1 email "[email protected]"(integer) 0127.0.0.1:6379> HGET user:1 email127.0.0.1:6379> HSETNX user:1 referrer "bob"(integer) 1127.0.0.1:6379> HGET user:1 referrer"bob"A practical use case: stamp a created_at field exactly once when the object is first written. Because HSETNX is atomic, two concurrent writers cannot both win — only the first call returns 1.
HSET user:1 name "Ada" email "[email protected]"
HSETNX user:1 email "[email protected]"
HGET user:1 email
HSETNX user:1 referrer "bob"
HGET user:1 referrerHash vs many string keys — when to use which
Section titled “Hash vs many string keys — when to use which”| Hash (one key) | Many string keys | |
|---|---|---|
| Memory | Compact (listpack for small hashes) | Higher overhead per key |
| Fetch one field | HGET user:1 name | GET user:1:name |
| Fetch all fields | HGETALL user:1 | MGET user:1:name user:1:email ... |
| Atomic increment | HINCRBY | INCR user:1:points |
| TTL per field | Not possible | Yes (each key has its own TTL) |
Rule of thumb: prefer one hash per object. Use separate string keys only if you need per-field TTL.