Redis Hashes
Redis hashes store field-value pairs under a single key. Use them when you need to read, write, or increment individual fields of an object without retrieving or rewriting the entire payload. For single values, opaque blobs, or cached documents read as a whole, use Redis Strings.
When Redis Hashes Are the Right Choice
What is a Redis hash, and when should you choose it over a string?
Use this decision rule:
• Choose Hashes when your data has multiple distinct fields (such as user profile attributes, session state, shopping cart lines, or multi-metric counters) and you need to read or update individual fields without transferring and rewriting the whole object.
• Choose Strings when your value is an opaque blob (e.g. compressed HTML, image bytes), a standalone counter, or a cached JSON payload that your application always reads and writes as one unified document. See the Redis Strings guide for string patterns.
• Choose RedisJSON (Redis Stack) when you have deeply nested objects, nested arrays, or require JSONPath querying (such as
$.address.city). Native Redis hashes are strictly flat field-value dictionaries.Key Characteristics
• Flat field-value mapping: Every field name and field value is stored as a binary-safe string.
• Memory efficiency: Small hashes are encoded using compact listpack (or ziplist in older versions), consuming dramatically less RAM than storing each field as a separate top-level key.
• O(1) field operations: HSET, HGET, HDEL, HEXISTS, and HINCRBY execute in constant time regardless of hash size.
• O(N) full-hash commands: HGETALL, HKEYS, and HVALS scale linearly with the number of fields in the hash—dangerous on large hashes.
• Strict type separation: Redis enforces types. Running a string command like GET or SET on a hash key immediately fails with a WRONGTYPE error.
Basic Operations
Core CLI commands to create, read, verify, and delete fields in a Redis hash:
# Set multiple fields at once (HSET creates key if it doesn't exist)
HSET user:1000 name "Alice" email "alice@example.com" role "admin" age 28
# Read a single field → "Alice"
HGET user:1000 name
# Read multiple fields in one roundtrip
HMGET user:1000 name email role
# Set a field ONLY if it does not already exist (returns 1 if set, 0 if existed)
HSETNX user:1000 nickname "ally"
# Check if a field exists (1 = yes, 0 = no)
HEXISTS user:1000 phone
# Delete one or more fields
HDEL user:1000 nickname phone
# Count total fields in hash
HLEN user:1000
# Inspect field names and values
HKEYS user:1000 # Returns ["name", "email", "role", "age"]
HVALS user:1000 # Returns ["Alice", "alice@example.com", "admin", "28"]
# WRONGTYPE Demonstration: Cannot call string commands on a hash key
GET user:1000
# (error) WRONGTYPE Operation against a key holding the wrong kind of valueBulk Read & Scanning Caution (HGETALL vs HSCAN)
Why HGETALL can hurt production performance:
HGETALL retrieves every field and value in a single blocking command (O(N)). If a hash grows to tens of thousands of fields, HGETALL blocks the single-threaded Redis event loop while transferring megabytes across the wire.
Production recommendations:
• Small hashes (< 100 fields): HGETALL is completely safe and fast.
• Known fields: Always prefer HMGET to fetch only the exact fields your service requires.
• Large or unpredictable hashes: Use HSCAN for non-blocking, cursor-based pagination:
# 1. Safe on small hashes: returns alternating field and value pairs
HGETALL user:1000
# 2. Preferred when you only need specific attributes:
HMGET user:1000 name email
# 3. Non-blocking iteration for large hashes (starts at cursor 0)
HSCAN user:1000 0 COUNT 10
# Returns: [new_cursor, [field1, val1, field2, val2, ...]]
# When returned cursor is 0, iteration is complete.
# Filter fields during scan:
HSCAN user:1000 0 MATCH preference:* COUNT 50Numeric Fields & Atomic Counters
Hash fields can hold integer and floating-point strings that Redis manipulates atomically with HINCRBY and HINCRBYFLOAT.
Unlike keeping separate string keys (e.g. user:1000:views, user:1000:likes), grouping counters inside a hash bundles related metrics under a single namespace and simplifies deletion:
# Initialize counters
HSET video:804 views 0 likes 0 rating 4.5
# Atomic integer increments and decrements
HINCRBY video:804 views 1 # Increment views by 1 → 1
HINCRBY video:804 likes 10 # Add 10 likes → 10
HINCRBY video:804 likes -1 # Decrement likes → 9
# Atomic floating-point increment
HINCRBYFLOAT video:804 rating 0.25 # Increase rating → 4.75
HINCRBYFLOAT video:804 rating -0.10 # Decrease rating → 4.65
# Fetch current totals
HMGET video:804 views likes ratingUse Case: User Profile & Session State
User entities frequently have varying access patterns: authentication checks need the password hash, profile pages need display attributes, and last-active timestamps update frequently without changing profile data:
# Store initial user profile
HSET user:42 \
username "alex_dev" \
email "alex@example.com" \
plan "pro" \
active "true" \
login_count 0
# 1. Partial update: User logs in (only update login count and timestamp)
HINCRBY user:42 login_count 1
HSET user:42 last_login_at "2026-09-18T16:00:00Z"
# 2. Fast authorization check: fetch only status and tier (saves bandwidth)
HMGET user:42 active plan
# Returns: ["true", "pro"]
# 3. Profile edit: change plan without touching login stats
HSET user:42 plan "enterprise"Use Case: Multi-Counter Metrics Bag
Collect real-time dashboard stats partitioned by entity and time window without creating hundreds of top-level keys:
# Single hash per endpoint per hour
HINCRBY stats:api:checkout:2026091816 total_requests 1
HINCRBY stats:api:checkout:2026091816 status_200 1
HINCRBY stats:api:checkout:2026091816 latency_ms_sum 42
# When an error occurs
HINCRBY stats:api:checkout:2026091816 status_500 1
# Read all metrics at end of hour and set key expiry
HGETALL stats:api:checkout:2026091816
EXPIRE stats:api:checkout:2026091816 86400Use Case: Shopping Cart Line Items
Model an active e-commerce basket where field keys are item IDs and field values are quantities:
# User adds items to basket
HSET cart:user:99 sku_keyboard 1 sku_mouse 2 sku_cable 3
# User increases keyboard quantity
HINCRBY cart:user:99 sku_keyboard 1 # Quantity becomes 2
# User removes cable
HDEL cart:user:99 sku_cable
# Check if user has a mouse in basket
HEXISTS cart:user:99 sku_mouse # Returns 1
# Total unique products in cart
HLEN cart:user:99 # Returns 2
# Entire basket checkout (read all items)
HGETALL cart:user:99
# Clear cart after checkout
DEL cart:user:99Expiration: Key-Level TTL vs Per-Field TTL (HEXPIRE)
Standard Redis expiration applies to the entire hash key. All fields expire and are deleted together when the key TTL finishes:
# Standard: Set expiration on the entire hash (e.g. 1 hour)
EXPIRE user:session:xyz 3600
TTL user:session:xyz
Field-Level TTL (Redis 7.4+):
Starting in Redis 7.4, Redis introduced native per-field expiration commands (HEXPIRE, HPEXPIRE, HTTL, and HEXPIREAT).
• Syntax: HEXPIRE key seconds FIELDS numfields field [field ...]
• Example:
HEXPIRE user:42 300 FIELDS 1 temp_token
HTTL user:42 FIELDS 1 temp_token
• Important: Check your Redis deployment version before using HEXPIRE. Many managed cloud services (such as older AWS ElastiCache or Azure Cache instances) run Redis 7.0 or 7.2 where field-level TTL is not yet supported.
Common Redis Hash Mistakes
1. Running HGETALL in hot API paths on unbounded hashes:
If user activity or tags add thousands of fields to a hash, HGETALL can saturate network bandwidth and block the Redis event thread. Use HMGET for known fields or HSCAN for pagination.
2. Faking nested objects with JSON strings inside fields:
Storing complex JSON strings as values inside a hash (e.g. HSET user:1 address '{"street":"...","city":"..."}') negates the benefits of both tools. If you need deep nesting, use RedisJSON. If you use hashes, keep fields flat (e.g. address_street, address_city).
3. The "One Mega-Hash" anti-pattern:
Do not put all users in a single hash (e.g. HSET all_users 1 "Alice" 2 "Bob"). This creates a massive key that cannot be partitioned across Redis Cluster nodes and causes massive latency spikes on resharding or backup. Always use one hash per business entity (user:1, user:2).
4. Using a hash when a single string counter is simpler:
If you only need a single metric (like a global request sequence or a single cache flag), a plain string with INCR or SET is faster and uses less memory than creating a single-field hash.
5. Calling string commands on hashes (WRONGTYPE):
Calling GET user:1000 or INCR user:1000 will fail with WRONGTYPE. Always use the H-prefixed commands: HGET, HSET, HINCRBY.
Summary: Hashes vs Strings Cheat Sheet
| Requirement | Use Redis Hash | Use Redis String |
|---|---|---|
| Partial field updates | Yes: HSET / HINCRBY without touching other fields | No: Requires reading, modifying, and rewriting full payload |
| Opaque cache blob (HTML, gzip, JSON) | No: Unnecessary field overhead | Yes: SET and GET the raw binary or text directly |
| Multi-counter collection | Yes: Bundles views, likes, shares under one key | Only if using separate keys for each metric |
| Individual field expiration | Yes (Redis ≥ 7.4 via HEXPIRE) | Yes (each string key has its own TTL) |
| Deeply nested JSON structure | No (use RedisJSON instead) | Store serialized blob (or use RedisJSON) |
| Memory for many small objects | Excellent (listpack / ziplist compression) | Higher overhead if split across many individual keys |
Related
Redis Strings Guide
Learn when to use strings for whole-value caching, single counters, and distributed locks.
Redis Data Structures Overview
Compare hashes, strings, sets, sorted sets, lists, and streams side-by-side.
Official Redis Hashes Documentation (Redis.io)
Comprehensive command index and internal encoding specifications for Redis hashes.
Latency Targets & Benchmarks
Understand the impact of O(N) operations like HGETALL on single-threaded Redis latency.
Knowledge is power.