Grokking Redis
Vote

0% completed

​

2.3 Hashes

What a Hash Is

Field-Level Commands

Writing

Reading

Atomic Updates

Iterating Large Hashes

Field Expiration

Hash Versus JSON String

Where a Hash Wins

Where a JSON String Wins

Middle Ground: the JSON Data Type

Choosing Between Them

Using This in an Interview

FAQs

Key Takeaways

Consider one user record stored two ways. As a JSON string under user:1234, changing the user's plan means downloading the whole document, editing it in the application, and uploading it again. As a Redis hash under the same key, it is one command, HSET user:1234 plan premium, and the other fields are never touched.

A Redis hash is a map of field names to values stored under a single key. For user:1234, the fields are name, email, plan, and login_count. Because the server understands this structure, you can read one field, change one field, or increment one field without moving the rest. This lesson keeps user:1234 as its running example.

What a Hash Is

A hash maps string fields to string values. Redis sees every value as a string, though the numeric commands can treat a value as an integer or a decimal. A hash is also flat: a field cannot contain another hash. Nested data must be flattened into field names such as address.city, or stored as a serialized string in one field.

A single hash can hold up to 2^32 fields (about four billion), but practical sizes are far smaller. The hash counts as one key for TTL, eviction, and memory accounting, which matters for the bucketing technique in the next lesson.

Redis Hash Structure
Redis Hash Structure

Field-Level Commands

Writing

HSET key field value [field value ...] sets one or more fields, creates the hash if it does not exist, and returns how many fields were newly added. Older code uses HMSET, which is deprecated since Redis 4.0 in favor of HSET with several pairs. HSETNX sets a field only if it does not already exist, which is useful for initializing a value exactly once.

redis> HSET user:1234 name "Arslan Ahmad" email "arslan@example.com" plan pro login_count 42 (integer) 4 redis> HSET user:1234 plan premium (integer) 0

The second reply is 0 because plan already existed, so no field was added, although its value changed.

Reading

HGET returns one field, and HMGET returns several fields in one round trip, with (nil) for any missing field. HGETALL returns every field and value, HKEYS returns only the field names, and HVALS returns only the values. HLEN returns the number of fields, HEXISTS checks for one field, and HSTRLEN returns the length of one value.

redis> HMGET user:1234 name plan nickname 1) "Arslan Ahmad" 2) "premium" 3) (nil)

Atomic Updates

HINCRBY adds an integer to a numeric field, and HINCRBYFLOAT does the same for decimals. Like INCR on strings, these commands are atomic, and a missing field is treated as zero. As a result, one hash can hold a group of counters, such as likes, views, and shares for a post, and each can be updated independently without locks.

Redis Hash Commands
Redis Hash Commands

Iterating Large Hashes

HGETALL is O(N) in the number of fields. On a hash with a million fields it builds one huge reply, and Redis serves no other client while it does so. HSCAN key cursor [MATCH pattern] [COUNT n] returns the fields in small batches together with a cursor for the next call, and the iteration ends when the cursor returned is 0. For a small hash, which Redis stores in a compact encoding that the next lesson describes, HSCAN usually returns every field in its first call regardless of COUNT, which is harmless because the hash is small.

HDEL removes fields, and DEL or UNLINK removes the whole hash. UNLINK frees the memory in a background thread, so it is the safer choice for a large hash.

Field Expiration

Before Redis 7.4, a TTL could apply only to the whole key, so expiring the hash deleted every field at once. Redis 7.4 added per-field expiry: HEXPIRE and HPEXPIRE set a relative TTL on individual fields, HEXPIREAT and HPEXPIREAT set an absolute time, HTTL and HPTTL read the remaining time, and HPERSIST removes a field's expiry. The commands that set an expiry return one result code per field, where 1 means the expiry was set and -2 means the field does not exist, while HTTL and HPTTL return one remaining time per field.

redis> HSET user:1234 reset_token "k7Q2x" (integer) 1 redis> HEXPIRE user:1234 900 FIELDS 1 reset_token 1) (integer) 1

Redis 8.0 added three commands that combine the read or write with the expiry. HSETEX sets fields and their TTL in one command, HGETEX reads fields and sets or removes their TTL, and HGETDEL reads fields and deletes them. For example, HSETEX user:1234 EX 900 FIELDS 1 reset_token k7Q2x stores the token and its 15-minute TTL atomically. Since Redis 8.8, keyspace notifications can also report individual hash fields that change or expire.

Field TTLs help when attributes of one object expire on different schedules, such as a session hash whose CSRF token lasts 15 minutes while the session lasts a day. They cost some extra memory and tracking work per field. Valkey added hash field expiration in version 9.0.

Hash Versus JSON String

The choice between a hash and a JSON string is the central design decision for objects, and the answer depends on how the object is accessed.

Where a Hash Wins

  • Partial reads. If a request needs only the email, HGET returns a few bytes. With a JSON string, you transfer and parse the whole document to read one field.
  • Partial writes. Changing the plan is one command that touches one field. With a JSON string, the client reads, deserializes, modifies, serializes, and writes the whole value. This costs bandwidth and CPU, and it creates a race: two clients doing it at the same time can each overwrite the other's change, because each started from the old version.
  • Atomic field operations. HINCRBY gives counters without locks inside the object. A JSON string has no equivalent without a script.
  • Memory for small objects. Small hashes use a compact encoding that is often smaller than the same JSON, because there are no quotes, braces, or repeated punctuation. Storing each attribute as a separate string key is the worst option, because every key carries its own overhead.
  • Inspection. You can read and edit individual fields with standard tools such as redis-cli.
Redis Hash vs. JSON String
Redis Hash vs. JSON String

Where a JSON String Wins

  • Nested or complex structure. Hashes are flat, while arrays, nested objects, and mixed types fit naturally in JSON.
  • Whole-object access. If you always read and replace the entire object, one GET and one deserialization is the simplest and fastest path.
  • Typed values. Hash values are strings, so the client converts numbers, booleans, and nulls. JSON keeps the types.
  • Compression and formats. You can compress a large serialized object or use a compact binary format, which a hash cannot do per object.
  • Simplicity. One string key per object behaves the same in every client, tool, and cluster setup.

Middle Ground: the JSON Data Type

Redis Open Source 8 includes a native JSON data type, which earlier versions provided through the separate RedisJSON module or Redis Stack. It stores real JSON documents on the server and lets you read and update nested paths atomically, such as JSON.NUMINCRBY user:1234 $.profile.age 1. It gives path-level access with full nesting, at some extra memory and CPU cost compared with a plain hash. Consider it when you need nested structure and partial updates together. Some managed services, and the core Valkey server, do not include it, so check before depending on it.

Choosing Between Them

Use a hash when an object has a modest number of flat fields that you read or update independently and often. Use a JSON string when you treat the object as one replaceable value, or when it is deeply nested. A user profile with counters and frequently changed settings suits a hash, while a cached API response that is rendered once and served whole suits a string.

Redis hash vs. JSON string vs. the JSON data type built into Redis Open Source 8.
Redis hash vs. JSON string vs. the JSON data type built into Redis Open Source 8.

Using This in an Interview

Describe a hash as a map stored under one key that supports field-level reads, writes, and atomic increments, and compare it with a JSON string by access pattern.

When is a JSON string the better choice? When the object is nested, or when it is always read and replaced as a whole, because one GET and one deserialization is then the simplest path.

Can one field expire before the rest? Yes, since Redis 7.4 with HEXPIRE, and since 8.0 HSETEX sets a field and its TTL in one command.

FAQs

What is the difference between HSET and HMSET?

HMSET is the older command for setting several fields and is deprecated since Redis 4.0. HSET accepts several field and value pairs, so use it everywhere.

Is HGETALL safe to use?

For small hashes, yes. For large ones it keeps the main thread busy while it builds a large reply, so use HSCAN for iteration or HMGET for known fields.

Can I expire individual fields of a hash?

In Redis 7.4 and later, yes, with HEXPIRE and related commands, and Redis 8.0 added HSETEX, HGETEX, and HGETDEL. On earlier versions TTL applies only to the whole key, so the workaround is separate keys, or expiry timestamps stored in fields and checked by the application.

Can a hash contain another hash?

No. Hashes are flat. Flatten the structure, store nested data as a serialized value in one field, or use the JSON data type for real nested documents.

Key Takeaways

  • A hash is a flat map of string fields to string values under one key. You read, write, and increment individual fields without touching the rest.
  • Field-level commands remove read-modify-write races. HSET, HGET, HMGET, and HINCRBY act on single fields atomically, and HSCAN iterates large hashes in batches.
  • Choose a hash or a JSON string by access pattern. Hashes suit flat fields accessed independently, JSON strings suit whole-object reads and nested data, and the JSON data type in Redis 8 sits between them.
  • TTL is per key unless you use field expiration. Redis 7.4 added HEXPIRE for individual fields, and Redis 8.0 added HSETEX, HGETEX, and HGETDEL.

Reading Progress

0%


Vote for new content

On This Page

What a Hash Is

Field-Level Commands

Writing

Reading

Atomic Updates

Iterating Large Hashes

Field Expiration

Hash Versus JSON String

Where a Hash Wins

Where a JSON String Wins

Middle Ground: the JSON Data Type

Choosing Between Them

Using This in an Interview

FAQs

Key Takeaways