Redis Cheatsheet

Hashes

Use this Redis reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Command table

A hash is a map from field to string value inside one key. It is the natural fit for an object, and small hashes are stored as a flat listpack that costs a fraction of the equivalent set of top-level keys.

CommandWhat it doesComplexity
HSET key f v [f v ...]Set fields, return how many were newO(n) fields
HSETNX key f vSet one field only if it does not existO(1)
HGET key fOne field's value, nil if absentO(1)
HMGET key f [f ...]Several fields, nil per missing fieldO(n) fields
HGETALL keyEvery field and value, flattenedO(n) hash size
HDEL key f [f ...]Delete fields, return how many were removedO(n) fields
HEXISTS key f1 or 0O(1)
HLEN keyNumber of fieldsO(1)
HSTRLEN key fByte length of one field's valueO(1)
HKEYS keyEvery field nameO(n)
HVALS keyEvery valueO(n)
HINCRBY key f nAdd a signed integer to a fieldO(1)
HINCRBYFLOAT key f fAdd a float to a fieldO(1)
HRANDFIELD key [n [WITHVALUES]]Random fields, negative n allows repeats (6.2+)O(n) returned
HSCAN key cursor [MATCH p] [COUNT n] [NOVALUES]Cursor iterate, NOVALUES is 7.4+O(1) per call
HGETEX / HGETDELGet with TTL options, get and delete (8.0+)O(n) fields

HMSET is a deprecated alias for HSET and has been variadic-equivalent since 4.0. Use HSET.

Reading and writing

HSET hu:user:1042 name "Ada" email "ada@example.com" plan free
# (integer) 3, the count of NEW fields, so a re-run returns 0

HGET  hu:user:1042 email                 # "ada@example.com"
HMGET hu:user:1042 name plan missing     # "Ada", "free", (nil)
HGETALL hu:user:1042                     # field, value, field, value, ...
HDEL  hu:user:1042 plan                  # (integer) 1
HLEN  hu:user:1042                       # (integer) 2
HEXISTS hu:user:1042 plan                # (integer) 0

HGETALL returns a flat array in RESP2 and a real map in RESP3, so a client on HELLO 3 hands you a dict while a RESP2 client hands you alternating entries. HDEL of the last field deletes the key itself, and HLEN on a missing key is 0, so there is no way to distinguish an empty hash from an absent one. Empty hashes do not exist.

HGETALL on a hash with 500,000 fields is O(n) on a single-threaded server and ships the whole thing over the wire. Use HMGET for the fields you need, or HSCAN to walk it in batches.

Counters inside a hash

HINCRBY gives you a whole panel of counters under one key, with one expiry and one memory allocation.

HINCRBY hu:stats:post:42 views 1
HINCRBY hu:stats:post:42 likes 1
HINCRBY hu:stats:post:42 views 5
HINCRBYFLOAT hu:cart:9 total 19.99
HGETALL hu:stats:post:42       # views 6, likes 1
EXPIRE  hu:stats:post:42 86400 # one TTL covers every counter

Same rules as INCR: the field must parse as an int64, a missing field starts at 0, the result can go negative, and HINCRBYFLOAT returns a string. There is no HDECRBY, pass a negative number.

Iterating and sampling

# Walk a large hash without blocking
HSCAN hu:user:1042 0 MATCH 'pref:*' COUNT 100
HSCAN hu:user:1042 0 NOVALUES              # field names only, 7.4+

# Random sampling
HRANDFIELD hu:user:1042 2 WITHVALUES       # 2 distinct fields plus values
HRANDFIELD hu:user:1042 -5                 # 5 draws WITH repetition
HRANDFIELD hu:user:1042                    # exactly one field name

The sign of the count is the whole API: positive means distinct and capped at the hash size, negative means repeats allowed and the exact count you asked for. SRANDMEMBER, ZRANDMEMBER, and HRANDFIELD all share that convention.

Per-field TTL (7.4+)

Until 7.4 a TTL applied to the whole key, which is why people fell back to N string keys whenever individual fields had to expire. That is fixed.

CommandWhat it does
HEXPIRE key sec [NX XX GT LT] FIELDS n f [f ...]TTL per field, in seconds
HPEXPIRE key ms ... FIELDS n f ...Same, milliseconds
HEXPIREAT / HPEXPIREATAbsolute deadline per field
HTTL / HPTTL key FIELDS n f ...Remaining TTL per field
HEXPIRETIME / HPEXPIRETIMEAbsolute expiry time per field
HPERSIST key FIELDS n f ...Remove a field's TTL
HSET    hu:session:abc csrf tok123 uid 1042
HEXPIRE hu:session:abc 300 FIELDS 1 csrf   # 1) (integer) 1
HTTL    hu:session:abc FIELDS 1 csrf       # 1) (integer) 300
HPERSIST hu:session:abc FIELDS 1 csrf      # 1) (integer) 1

The FIELDS numfields block is mandatory and the count must match, which trips everyone the first time. Return codes per field: 1 set, 0 refused by an NX/XX/GT/LT condition, -2 no such field, and 2 when the TTL was in the past so the field was deleted immediately. When the last field with a TTL expires the key disappears.

Note that per-field TTL forces the hash out of listpack encoding, so it costs memory.

Hash or N string keys

One hashN string keys
Memory for a small objectFar less, one listpack allocation~50-100 bytes overhead each
Read all fieldsOne HGETALLOne MGET, needs every key name
Expire one fieldHEXPIRE (7.4+)Native, any version
Atomic multi-field writeHSET f v f v, one commandMSET, also atomic
Cluster localityAll fields on one slot, so scripts workKeys scatter, CROSSSLOT errors
Secondary index on a valueNo, hashes are not indexedNo, same
OBJECT ENCODING hu:user:1042      # "listpack" while small, "hashtable" after

Encoding flips to hashtable on the first write that exceeds hash-max-listpack-entries (128) or hash-max-listpack-value (64 bytes), and it never flips back even if you delete fields down again. That one-way conversion is why the classic advice is to shard a huge logical hash into buckets:

# 10M ids in 100k hashes of ~100 fields each stays in listpack encoding
HSET hu:emails:bucket:0042 1042 "ada@example.com"

Rules of thumb:

- Use a hash for an object whose fields you read or write individually. - Use one string holding JSON when you always read and write the whole thing, since it is one round trip and the client does the parsing. - Never use a hash as a general index. There is no "find the field whose value is X" short of HSCAN over everything.