Hashes
A hash maps field names to string values under a single key, much like a row or a small JSON object. You can read or change one field without touching the others, increment numeric fields in place, and give individual fields their own time to live. Hashes are the usual way to store user profiles, shopping carts, per-object settings, and groups of related counters.
Create, read, update, delete
Section titled “Create, read, update, delete”HSET sets one or more fields and creates the hash if needed. It replies with the number of fields that were new.
127.0.0.1:6379> HSET user:42 name "Ada" email "ada@example.com" visits 0(integer) 3127.0.0.1:6379> HGET user:42 name"Ada"127.0.0.1:6379> HMGET user:42 name plan1) "Ada"2) (nil)127.0.0.1:6379> HSET user:42 email "ada@example.org" plan "pro"(integer) 1127.0.0.1:6379> HGETALL user:421) "name"2) "Ada"3) "email"4) "ada@example.org"5) "visits"6) "0"7) "plan"8) "pro"127.0.0.1:6379> HEXISTS user:42 plan(integer) 1127.0.0.1:6379> HLEN user:42(integer) 4127.0.0.1:6379> HDEL user:42 plan(integer) 1Don’t rely on the order of fields in HGETALL, HKEYS and HVALS
replies. Under RESP3, HGETALL returns a map.
More ways to work with fields:
- HSETNX sets a field only if it does not exist.
- HKEYS, HVALS and HSTRLEN return field names, values, or one value’s length.
- HGETDEL reads fields and deletes them in one step.
- HRANDFIELD picks random fields.
- HSCAN iterates a large hash in pages, with
NOVALUESto fetch names only.
Deleting the last field deletes the key. DEL removes the whole hash at once.
Counters in a hash
Section titled “Counters in a hash”HINCRBY and HINCRBYFLOAT change a numeric field atomically. A missing field counts as 0, so one hash can hold a whole set of counters:
127.0.0.1:6379> HINCRBY stats:2026-10-02 signups 1(integer) 1127.0.0.1:6379> HINCRBY stats:2026-10-02 orders 3(integer) 3127.0.0.1:6379> HINCRBYFLOAT stats:2026-10-02 revenue 59.90"59.9"127.0.0.1:6379> HINCRBY user:42 name 1(error) ERR hash value is not an integerExpiry, for the key and for single fields
Section titled “Expiry, for the key and for single fields”Like any key, a hash can expire as a whole: EXPIRE sets a time to live for the key, and when it passes every field goes.
Hashes can also expire individual fields, which is handy for a cache of
many small items under one key, or for one-time tokens stored next to
long-lived data. The field commands take the time first and then
FIELDS numfields field [field ...]:
127.0.0.1:6379> HSET session:9f2c user "42" csrf "a81c" otp "553201"(integer) 3127.0.0.1:6379> HEXPIRE session:9f2c 300 FIELDS 1 otp1) (integer) 1127.0.0.1:6379> HTTL session:9f2c FIELDS 3 otp user nosuch1) (integer) 3002) (integer) -13) (integer) -2127.0.0.1:6379> HPERSIST session:9f2c FIELDS 1 otp1) (integer) 1Each field gets its own result code.
| Command | Code | Meaning |
|---|---|---|
HEXPIRE family |
1 |
The time to live was set |
0 |
Not set, because an NX, XX, GT or LT condition did not hold |
|
2 |
The time was 0 or already past, so the field was deleted | |
-2 |
No such field (or no such key) | |
HTTL family |
n |
Seconds (or milliseconds for HPTTL) left |
-1 |
The field has no time to live | |
-2 |
No such field | |
HPERSIST |
1 |
The time to live was removed |
-1 |
The field had no time to live | |
-2 |
No such field |
HEXPIRE takes seconds and
HPEXPIRE milliseconds;
HEXPIREAT and HPEXPIREAT
take a Unix time. HEXPIRETIME and
HPEXPIRETIME report the expiry as a Unix time.
The NX, XX, GT and LT options work as they do for EXPIRE.
You can also set or read values and their expiry together:
127.0.0.1:6379> HSETEX cache:prices EX 60 FIELDS 2 sku:1 "9.99" sku:2 "4.50"(integer) 1127.0.0.1:6379> HGETEX cache:prices EX 120 FIELDS 1 sku:11) "9.99"127.0.0.1:6379> HSETEX cache:prices FNX FIELDS 1 sku:1 "8.99"(integer) 0- HSETEX sets all the fields or none:
FNXonly if none of them exist,FXXonly if all of them do. It replies1when it set them and0when the condition stopped it. Without an expiry option the fields it sets lose any time to live they had;KEEPTTLkeeps it. - HGETEX returns the values and sets, changes, or
(with
PERSIST) removes their time to live.
Rules worth knowing:
- Overwriting a field with
HSETclears that field’s time to live. - When the last field of a hash expires, the key is deleted.
- Field expiries are saved in snapshots and the AOF, and replicas apply them the same way, so they survive restarts and failovers.
- An expired field fires a
hexpiredkeyspace notification when those are enabled.
Common patterns
Section titled “Common patterns”Objects. Store one entity per key (user:42) with a field per
attribute. Reading one attribute with HGET avoids transferring and
parsing the whole object, which you would have to do with a JSON string.
Shopping cart. HINCRBY cart:42 sku:123 1 adds an item, HDEL removes
it, HGETALL shows the cart, and EXPIRE cart:42 604800 clears abandoned
carts after a week.
Many small cache entries under one key. Put entries in one hash and
give each field its own HEXPIRE. Expiring fields drop out on their own,
and DEL clears the whole group at once.
Grouped counters. One hash per day with a field per metric, read in one
HGETALL.
Limits and performance
Section titled “Limits and performance”Field names and values are strings up to proto-max-bulk-len (512 MB by
default). Single-field reads and writes take constant time; HGETALL,
HKEYS and HVALS return every field, so use HSCAN on hashes with many
fields.
Commands
Section titled “Commands”| Command | What it does |
|---|---|
| HSET | Set fields |
| HSETNX | Set a field only if it is missing |
| HMSET | Older form of HSET that replies OK |
| HGET / HMGET | Read fields |
| HGETALL | All fields and values |
| HKEYS / HVALS | All field names or values |
| HEXISTS | Whether a field exists |
| HLEN | Number of fields |
| HSTRLEN | Length of a field’s value |
| HDEL | Delete fields |
| HGETDEL | Read and delete fields |
| HINCRBY / HINCRBYFLOAT | Increment a field |
| HRANDFIELD | Random fields |
| HSCAN | Iterate fields in pages |
| HSETEX | Set fields with an expiry |
| HGETEX | Read fields and change their expiry |
| HEXPIRE / HPEXPIRE | Field time to live in seconds or milliseconds |
| HEXPIREAT / HPEXPIREAT | Field expiry at a Unix time |
| HTTL / HPTTL | Time left per field |
| HEXPIRETIME / HPEXPIRETIME | Expiry per field as a Unix time |
| HPERSIST | Remove a field’s time to live |
From your application
Section titled “From your application”import hotkv
client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)
client.hset("user:42", {"name": "Ada", "visits": 0})client.hincrby("user:42", "visits", 1)print(client.hgetall("user:42"))
client.hset("session:9f2c", {"user": "42", "otp": "553201"})client.hexpire("session:9f2c", 300, ["otp"])print(client.httl("session:9f2c", ["otp", "user"])) # [300, -1]client.close()import { HotKV } from "@hotkv/client";
const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });
await client.hSet("user:42", { name: "Ada", visits: 0 });await client.hIncrBy("user:42", "visits", 1);console.log(await client.hGetAll("user:42"));
await client.hSet("session:9f2c", { user: "42", otp: "553201" });await client.hExpire("session:9f2c", 300, ["otp"]);console.log(await client.hTtl("session:9f2c", ["otp", "user"]));await client.close();client, err := hotkv.NewClient(&hotkv.Options{Addr: "localhost:6379"})if err != nil { log.Fatal(err)}defer client.Close()ctx := context.Background()
client.HSet(ctx, "user:42", "name", "Ada", "visits", 0)client.HIncrBy(ctx, "user:42", "visits", 1)fields, _ := client.HGetAll(ctx, "user:42").Result()fmt.Println(fields)In the Go client, send the field-expiry commands through a pipeline’s Do.
import com.hotkv.HotKV;import java.util.Map;
try (HotKV client = HotKV.create("hotkv://localhost:6379")) { client.hset("user:42", Map.of("name", "Ada", "visits", "0")); client.hincrBy("user:42", "visits", 1); System.out.println(client.hgetAll("user:42"));
client.hset("session:9f2c", Map.of("user", "42", "otp", "553201")); client.hExpire("session:9f2c", 300, "otp"); System.out.println(client.hTtl("session:9f2c", "otp", "user"));}Was this page helpful?
Thanks for the feedback.