Hashes
|
This content was generated with the assistance of AI and should be verified against the official Redis documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Examples on this page target Redis Open Source 8.10 (8.10.2) with the Jedis 8 and Lettuce 7.7 client libraries. A hash is Redis’s map type — a single key holding an unordered set of field/value string pairs — and it is the natural fit for a record with a small, mostly-known set of attributes, such as a per-user shopping cart keyed by product id.
Core field commands
-
HSET key field value [field value …]— sets one or more fields, creating the hash ifkeydoesn’t exist yet. It always overwrites an existing field’s value. See HSET. -
HGET key field— reads a single field’s value, ornilif the field or the key doesn’t exist. See HGET. -
HMGET key field [field …]— reads several fields in one round trip, returningnilin the corresponding position for any field that doesn’t exist. See HMGET. -
HGETALL key— returns every field/value pair in the hash. Convenient for small hashes; for a large one, preferHSCANbelow to avoid blocking the server on one large reply. See HGETALL. -
HINCRBY key field increment— atomically adds an integer increment to a field, creating the field (from0) if it doesn’t exist yet. See HINCRBY. -
HSCAN key cursor [MATCH pattern] [COUNT count] [NOVALUES]— cursor-based iteration over a hash’s fields, the hash equivalent ofSCAN, safe to run against a large hash without blocking other clients. See HSCAN. -
HDEL key field [field …]— removes one or more fields; the key itself is removed once its last field is gone. See HDEL.
Field (subkey) expiration
Since Redis 7.4, individual hash fields can carry their own TTL, independent of the key’s own expiration (or lack of one) — a field expires and is removed on its own schedule without touching any other field in the hash:
-
HEXPIRE key seconds [NX \| XX \| GT \| LT] FIELDS numfields field [field …]and its millisecond twinHPEXPIREset a relative TTL on the named fields, with the sameNX/XX/GT/LTconditionsEXPIREsupports on a whole key. See HEXPIRE and HPEXPIRE. -
HTTL key FIELDS numfields field [field …]reports the remaining TTL per field (in seconds;HPTTLfor milliseconds), returning-1for a field with no TTL and-2for a field or key that doesn’t exist. See HTTL. -
HPERSIST key FIELDS numfields field [field …]clears a field’s TTL, turning it back into an ordinary, non-expiring field. See HPERSIST.
A field’s TTL survives an operation that only reads or overwrites its value; it is cleared only by a command
that deletes or replaces the field outright, such as HDEL or a plain HSET on that field.
Redis 8.0 added three commands that fold a value operation and its expiration into one round trip:
-
HGETEX key [EX seconds \| PX milliseconds \| EXAT unix-time-seconds \| PXAT unix-time-milliseconds \| PERSIST] FIELDS numfields field [field …]reads fields and, optionally, sets or clears their TTL in the same call. See HGETEX. -
HSETEX key [FNX \| FXX] [EX seconds \| PX milliseconds \| EXAT unix-time-seconds \| PXAT unix-time-milliseconds \| KEEPTTL] FIELDS numfields field value [field value …]writes fields and sets their TTL atomically, withFNX/FXXto require that none, or all, of the fields already exist. See HSETEX. -
HGETDEL key FIELDS numfields field [field …]atomically reads and removes fields in one call — useful for a one-shot claim-and-consume read. See HGETDEL.
Encoding: listpack vs. hashtable
A small hash is stored as a listpack — a compact, contiguous sequence of field/value entries that is cheap to allocate and scan linearly. Redis switches a hash to a hashtable encoding, trading memory for O(1) lookups, as soon as either threshold configured by these directives is crossed:
-
hash-max-listpack-entries(default512) — the field-count ceiling for listpack encoding. -
hash-max-listpack-value(default64) — the byte-length ceiling, applied to every field name and every value, for listpack encoding.
The switch is one-way and lazy: it is evaluated on the write that would cross a threshold, and a hashtable
never converts back to a listpack even if fields are later deleted. OBJECT ENCODING key reports which one a
given hash currently uses.
Compact hashes and bulk import
Redis 8.10 added compact hashes, a memory-layout change for hashes that share the same field-name schema
(for example, one hash per user, each with the same attribute names): the shared field-name set is stored
once and referenced by every hash that uses it, instead of each hash repeating the field names. This is
purely an internal encoding — every existing hash command keeps its usual syntax and replies against a
compact hash. Conversion is lazy and can happen automatically (via the hash-min-template-entries /
hash-max-template-entries config directives) or be driven explicitly with the
HIMPORT command family: HIMPORT PREPARE declares a named,
shared field set once, and HIMPORT SET then creates each hash by sending only its values against that
prepared set — cutting the network and per-command overhead of loading many same-shaped hashes one HSET
call at a time. A hash using field expiration is never converted to a compact hash.
Subkey (hash-field) notifications
Redis 8.8 added subkey notifications, which extend keyspace notifications with the specific field (or
JSON path, or array element) an event touched, rather than only the key. For hashes this closes a real gap:
an HSET/HDEL/HEXPIRE against a wide hash previously told a subscriber only that the key changed, not
which field — and a hexpired event on a per-field TTL had no standard way to name the field that expired.
Subscribing to hash events, keyspace notifications in general, and the hexpired event specifically are
covered in Streams, Pub/Sub & Keyspace Notifications.
Hash vs. JSON document
A hash and a JSON document (JSON) can model the same record, but they fit
different shapes. Reach for a hash when the record is a flat bag of scalar fields that are read or updated
one at a time — HINCRBY on a counter field, or a TTL on a single field, are hash-only. Reach for JSON
when the record is naturally nested (arrays, sub-objects) or is queried/updated by path rather than whole-key — a hash has no nesting and no per-path operations, only flat fields.
Example: the Bookshop cart
The Bookshop cart is a hash bookshop:cart:<userId> (see JSON for the sibling
bookshop:book:<isbn> JSON documents it references): each field is an ISBN, each value the quantity of that
book in the cart, and each field carries its own TTL so an item silently drops out of the cart if it sits
untouched for too long.
> HSET bookshop:cart:42 9780000000001 2 9780000000002 1
(integer) 2
> HEXPIRE bookshop:cart:42 1800 FIELDS 2 9780000000001 9780000000002
1) (integer) 1
2) (integer) 1
> HTTL bookshop:cart:42 FIELDS 1 9780000000001
1) (integer) 1800
The same cart update in Jedis, adding a book to the cart and refreshing its TTL in one call with HSETEX:
package com.example.bookshop;
import redis.clients.jedis.RedisClient;
import redis.clients.jedis.params.HSetExParams;
import java.util.Map;
public class CartService {
private final RedisClient redis;
public CartService(RedisClient redis) {
this.redis = redis;
}
public void addToCart(String userId, String isbn, int quantity) {
String cartKey = "bookshop:cart:" + userId;
redis.hsetex(
cartKey,
HSetExParams.hSetExParams().ex(1800), // 30-minute TTL on this field
Map.of(isbn, String.valueOf(quantity)));
}
public static void main(String[] args) {
try (RedisClient redis = RedisClient.create("redis://localhost:6379")) {
new CartService(redis).addToCart("42", "9780000000001", 2); // "The Redis Way"
}
}
}
This derives from HSETEX.
Related pages
-
Streams, Pub/Sub & Keyspace Notifications — keyspace and subkey notifications, including the
hexpiredevent referenced above. -
JSON — the JSON document type used for the Bookshop
bookshop:book:<isbn>records, contrasted with hashes above.