Skip to content

Ask the HotKV docs

Answers come only from these docs and can be wrong; check the linked pages before you rely on them.

Hashes

Last updated

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.

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) 3
127.0.0.1:6379> HGET user:42 name
"Ada"
127.0.0.1:6379> HMGET user:42 name plan
1) "Ada"
2) (nil)
127.0.0.1:6379> HSET user:42 email "ada@example.org" plan "pro"
(integer) 1
127.0.0.1:6379> HGETALL user:42
1) "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) 1
127.0.0.1:6379> HLEN user:42
(integer) 4
127.0.0.1:6379> HDEL user:42 plan
(integer) 1

Don’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 NOVALUES to fetch names only.

Deleting the last field deletes the key. DEL removes the whole hash at once.

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) 1
127.0.0.1:6379> HINCRBY stats:2026-10-02 orders 3
(integer) 3
127.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 integer

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) 3
127.0.0.1:6379> HEXPIRE session:9f2c 300 FIELDS 1 otp
1) (integer) 1
127.0.0.1:6379> HTTL session:9f2c FIELDS 3 otp user nosuch
1) (integer) 300
2) (integer) -1
3) (integer) -2
127.0.0.1:6379> HPERSIST session:9f2c FIELDS 1 otp
1) (integer) 1

Each 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) 1
127.0.0.1:6379> HGETEX cache:prices EX 120 FIELDS 1 sku:1
1) "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: FNX only if none of them exist, FXX only if all of them do. It replies 1 when it set them and 0 when the condition stopped it. Without an expiry option the fields it sets lose any time to live they had; KEEPTTL keeps it.
  • HGETEX returns the values and sets, changes, or (with PERSIST) removes their time to live.

Rules worth knowing:

  • Overwriting a field with HSET clears 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 hexpired keyspace notification when those are enabled.

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.

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.

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
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()