Redis hashes are maps between string fields and string values. They’re ideal for representing objects and storing multiple related values under a single key.
HSET
Sets field-value pairs in a hash. Creates the key if it doesn’t exist.
Syntax
HSET key field value [field value ...]
The hash key. Created if it doesn’t exist.
The value to set for the field.
The number of fields that were added (not including updates to existing fields).
Time Complexity: O(1) for each field/value pair added, so O(N) for N pairs.
History:
- 4.0.0: Accepts multiple
field and value arguments.
Examples
redis> HSET myhash field1 "Hello"
(integer) 1
redis> HGET myhash field1
"Hello"
redis> HSET myhash field2 "World" field3 "!"
(integer) 2
redis> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
5) "field3"
6) "!"
HGET
Returns the value of a field in a hash.
Syntax
The field name to retrieve.
The value associated with field, or nil if field doesn’t exist.
Time Complexity: O(1)
Examples
redis> HSET myhash field1 "foo"
(integer) 1
redis> HGET myhash field1
"foo"
redis> HGET myhash field2
(nil)
HGETALL
Returns all fields and values in a hash.
Syntax
Array of field-value pairs (field1, value1, field2, value2, …).
Time Complexity: O(N) where N is the size of the hash.
HGETALL loads all fields into memory. For large hashes, use HSCAN to iterate incrementally.
Examples
redis> HSET myhash field1 "Hello" field2 "World"
(integer) 2
redis> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
HINCRBY
Increments the integer value of a hash field by the given number.
Syntax
HINCRBY key field increment
The increment value (can be negative).
The value of the field after incrementing.
Time Complexity: O(1)
Examples
redis> HSET myhash field 5
(integer) 1
redis> HINCRBY myhash field 1
(integer) 6
redis> HINCRBY myhash field -1
(integer) 5
redis> HINCRBY myhash field -10
(integer) -5
Additional Hash Commands
- HMGET: Get multiple field values
- HMSET: Set multiple fields (deprecated, use HSET)
- HSETNX: Set field only if it doesn’t exist
- HDEL: Delete one or more fields
- HEXISTS: Check if field exists
- HKEYS: Get all field names
- HVALS: Get all values
- HLEN: Get number of fields
- HSTRLEN: Get length of field value
- HINCRBYFLOAT: Increment by float
- HSCAN: Iterate fields
- HRANDFIELD: Get random field(s)
Use Cases
User Profile
Store user information:
redis> HSET user:1000 username "alice" email "[email protected]" age "30"
(integer) 3
redis> HGET user:1000 username
"alice"
redis> HGETALL user:1000
1) "username"
2) "alice"
3) "email"
4) "[email protected]"
5) "age"
6) "30"
redis> HINCRBY user:1000 age 1
(integer) 31
Session Store
Store session data:
redis> HSET session:abc123 user_id "1000" created "1640000000" last_seen "1640010000"
(integer) 3
redis> EXPIRE session:abc123 1800
(integer) 1
redis> HGET session:abc123 user_id
"1000"
Product Catalog
Store product details:
redis> HSET product:2000 name "Laptop" price "999.99" stock "50" category "electronics"
(integer) 4
redis> HMGET product:2000 name price
1) "Laptop"
2) "999.99"
redis> HINCRBY product:2000 stock -1
(integer) 49
Statistics Tracking
Track various counters:
redis> HINCRBY stats:website views 1
(integer) 1
redis> HINCRBY stats:website unique_visitors 1
(integer) 1
redis> HINCRBY stats:website page_errors 1
(integer) 1
redis> HGETALL stats:website
1) "views"
2) "1"
3) "unique_visitors"
4) "1"
5) "page_errors"
6) "1"
Rate Limiting by User
Track API calls per endpoint:
redis> HINCRBY rate:user:1000 "/api/users" 1
(integer) 1
redis> HINCRBY rate:user:1000 "/api/posts" 1
(integer) 1
redis> HGET rate:user:1000 "/api/users"
"1"
redis> EXPIRE rate:user:1000 60
(integer) 1
Best Practices
- Field Access: HGET, HSET are O(1) - very fast
- Full Hash: HGETALL is O(N) - use carefully on large hashes
- Iteration: Use HSCAN for large hashes instead of HGETALL
- Atomic Counters: HINCRBY is atomic and thread-safe
Memory Optimization
- Small Hashes: Redis uses efficient ziplist encoding for small hashes
- Field Count: Keep under 512 fields for optimal encoding
- Value Size: Keep values under 64 bytes for ziplist encoding
- Compression: Consider compressing large values before storing
Hash vs Separate Keys
Store related data together:redis> HSET user:1000 name "Alice" age "30"
(integer) 2
redis> HGET user:1000 name
"Alice"
Pros:
- Memory efficient for small objects
- Atomic operations on related data
- Logical grouping
Cons:
- Can’t set TTL per field
- Less flexible for partial updates
Store fields as individual keys:redis> SET user:1000:name "Alice"
OK
redis> SET user:1000:age "30"
OK
redis> GET user:1000:name
"Alice"
Pros:
- Individual TTL per field
- Better for selective access
- Works with Redis Cluster key distribution
Cons:
- More memory overhead
- Multiple operations needed
Patterns
Object Storage
Store complete objects:
# Store article
redis> HSET article:1 title "Redis Guide" author "alice" views "0" likes "0"
(integer) 4
# Update counters
redis> HINCRBY article:1 views 1
(integer) 1
redis> HINCRBY article:1 likes 1
(integer) 1
Multi-Tenant Counters
Track metrics per tenant:
redis> HINCRBY metrics:tenant1 api_calls 1
(integer) 1
redis> HINCRBY metrics:tenant1 errors 0
(integer) 0
redis> HINCRBY metrics:tenant2 api_calls 1
(integer) 1
redis> HGETALL metrics:tenant1
1) "api_calls"
2) "1"
3) "errors"
4) "0"
Store cart items:
# Add items to cart
redis> HSET cart:user:1000 product:1 "2"
(integer) 1
redis> HSET cart:user:1000 product:2 "1"
(integer) 1
# Update quantity
redis> HINCRBY cart:user:1000 product:1 1
(integer) 3
# Get all items
redis> HGETALL cart:user:1000
1) "product:1"
2) "3"
3) "product:2"
4) "1"
# Remove item
redis> HDEL cart:user:1000 product:2
(integer) 1
Feature Flags
Store feature flags per user:
redis> HSET features:user:1000 dark_mode "true" beta_access "false"
(integer) 2
redis> HGET features:user:1000 dark_mode
"true"
redis> HEXISTS features:user:1000 admin_panel
(integer) 0
Configuration Storage
Store application config:
redis> HSET config:app max_connections "100" timeout "30" debug "false"
(integer) 3
redis> HMGET config:app max_connections timeout
1) "100"
2) "30"
Hash vs Other Data Structures
| Feature | Hash | String (JSON) | Separate Keys |
|---|
| Memory | Efficient | More overhead | Most overhead |
| Field TTL | No | No | Yes |
| Atomic Ops | Yes (per field) | No | Yes (per key) |
| Field Access | O(1) | O(N) parse | O(1) |
| Use Case | Objects | Complex data | Distributed data |
Advanced Patterns
Lazy Loading
Load hash fields on demand:
# Check if field exists
redis> HEXISTS user:1000 profile_data
(integer) 0
# Load from database and cache
redis> HSET user:1000 profile_data "{...json...}"
(integer) 1
# Next access is from cache
redis> HGET user:1000 profile_data
"{...json...}"
Aggregation
Aggregate metrics from multiple hashes:
# Daily stats
redis> HINCRBY stats:2024-03-03 views 100
(integer) 100
redis> HINCRBY stats:2024-03-03 clicks 25
(integer) 25
# Calculate CTR
redis> HMGET stats:2024-03-03 views clicks
1) "100"
2) "25"
# CTR = 25/100 = 0.25
Batch Updates
Update multiple fields atomically:
redis> HSET order:1000 status "processing" updated_at "1640000000" processor "worker:1"
(integer) 3
Sparse Data
Store only non-null values:
# Only set fields that have values
redis> HSET user:1000 username "alice"
(integer) 1
# Don't set middle_name if not provided
redis> HSET user:1000 last_name "smith"
(integer) 1
Hashes cannot be nested. Each field value must be a string. For complex nested structures, consider storing JSON strings or using multiple hashes.
Common Operations
Check and Set
Bulk Retrieval
# Get multiple fields efficiently
redis> HMGET user:1000 username email age
1) "alice"
2) "[email protected]"
3) "30"
Field Counting
redis> HLEN user:1000
(integer) 3
Field Existence
redis> HEXISTS user:1000 username
(integer) 1
redis> HEXISTS user:1000 phone
(integer) 0