# Hashes

> Field-value maps stored under one key, with per-field counters and per-field expiry.

URL: https://docs.hotkv.com/data-types/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

[HSET](https://docs.hotkv.com/commands/hset/index.md) 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](https://docs.hotkv.com/commands/hsetnx/index.md) sets a field only if it does not exist.
- [HKEYS](https://docs.hotkv.com/commands/hkeys/index.md), [HVALS](https://docs.hotkv.com/commands/hvals/index.md) and
  [HSTRLEN](https://docs.hotkv.com/commands/hstrlen/index.md) return field names, values, or one value's
  length.
- [HGETDEL](https://docs.hotkv.com/commands/hgetdel/index.md) reads fields and deletes them in one step.
- [HRANDFIELD](https://docs.hotkv.com/commands/hrandfield/index.md) picks random fields.
- [HSCAN](https://docs.hotkv.com/commands/hscan/index.md) iterates a large hash in pages, with `NOVALUES`
  to fetch names only.

Deleting the last field deletes the key. [DEL](https://docs.hotkv.com/commands/del/index.md) removes the
whole hash at once.

## Counters in a hash

[HINCRBY](https://docs.hotkv.com/commands/hincrby/index.md) and [HINCRBYFLOAT](https://docs.hotkv.com/commands/hincrbyfloat/index.md)
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
```

## Expiry, for the key and for single fields

Like any key, a hash can expire as a whole: [EXPIRE](https://docs.hotkv.com/commands/expire/index.md)
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](https://docs.hotkv.com/commands/hexpire/index.md) takes seconds and
[HPEXPIRE](https://docs.hotkv.com/commands/hpexpire/index.md) milliseconds;
[HEXPIREAT](https://docs.hotkv.com/commands/hexpireat/index.md) and [HPEXPIREAT](https://docs.hotkv.com/commands/hpexpireat/index.md)
take a Unix time. [HEXPIRETIME](https://docs.hotkv.com/commands/hexpiretime/index.md) and
[HPEXPIRETIME](https://docs.hotkv.com/commands/hpexpiretime/index.md) 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](https://docs.hotkv.com/commands/hsetex/index.md) 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](https://docs.hotkv.com/commands/hgetex/index.md) 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](https://docs.hotkv.com/develop/keyspace-notifications/index.md) when those are
  enabled.

## 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

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

| Command | What it does |
| --- | --- |
| [HSET](https://docs.hotkv.com/commands/hset/index.md) | Set fields |
| [HSETNX](https://docs.hotkv.com/commands/hsetnx/index.md) | Set a field only if it is missing |
| [HMSET](https://docs.hotkv.com/commands/hmset/index.md) | Older form of `HSET` that replies `OK` |
| [HGET](https://docs.hotkv.com/commands/hget/index.md) / [HMGET](https://docs.hotkv.com/commands/hmget/index.md) | Read fields |
| [HGETALL](https://docs.hotkv.com/commands/hgetall/index.md) | All fields and values |
| [HKEYS](https://docs.hotkv.com/commands/hkeys/index.md) / [HVALS](https://docs.hotkv.com/commands/hvals/index.md) | All field names or values |
| [HEXISTS](https://docs.hotkv.com/commands/hexists/index.md) | Whether a field exists |
| [HLEN](https://docs.hotkv.com/commands/hlen/index.md) | Number of fields |
| [HSTRLEN](https://docs.hotkv.com/commands/hstrlen/index.md) | Length of a field's value |
| [HDEL](https://docs.hotkv.com/commands/hdel/index.md) | Delete fields |
| [HGETDEL](https://docs.hotkv.com/commands/hgetdel/index.md) | Read and delete fields |
| [HINCRBY](https://docs.hotkv.com/commands/hincrby/index.md) / [HINCRBYFLOAT](https://docs.hotkv.com/commands/hincrbyfloat/index.md) | Increment a field |
| [HRANDFIELD](https://docs.hotkv.com/commands/hrandfield/index.md) | Random fields |
| [HSCAN](https://docs.hotkv.com/commands/hscan/index.md) | Iterate fields in pages |
| [HSETEX](https://docs.hotkv.com/commands/hsetex/index.md) | Set fields with an expiry |
| [HGETEX](https://docs.hotkv.com/commands/hgetex/index.md) | Read fields and change their expiry |
| [HEXPIRE](https://docs.hotkv.com/commands/hexpire/index.md) / [HPEXPIRE](https://docs.hotkv.com/commands/hpexpire/index.md) | Field time to live in seconds or milliseconds |
| [HEXPIREAT](https://docs.hotkv.com/commands/hexpireat/index.md) / [HPEXPIREAT](https://docs.hotkv.com/commands/hpexpireat/index.md) | Field expiry at a Unix time |
| [HTTL](https://docs.hotkv.com/commands/httl/index.md) / [HPTTL](https://docs.hotkv.com/commands/hpttl/index.md) | Time left per field |
| [HEXPIRETIME](https://docs.hotkv.com/commands/hexpiretime/index.md) / [HPEXPIRETIME](https://docs.hotkv.com/commands/hpexpiretime/index.md) | Expiry per field as a Unix time |
| [HPERSIST](https://docs.hotkv.com/commands/hpersist/index.md) | Remove a field's time to live |

## From your application

**Python**

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

**Node.js**

```ts
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();
```

**Go**

```go
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`.

**Java**

```java
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"));
}
```
