Chapter 04 · Hashes and Object-Like Records
HSET/HGET/HMGET/HGETALL and Modeling Compact Field/Value Records
Model flat object-like records as Redis hashes, prove HSET/HGET/HMGET/HGETALL semantics and costs, and keep schema, TTL, memory, and missing-field boundaries explicit.
Learning outcomes
AtlasMart needs a fast read model for a product card: title, category, current display price, inventory label, merchandising status, and a schema-version marker. A developer proposes six independent Redis keys; another serializes the whole record into one JSON-looking String; a third reaches for a hash because the application usually reads or changes individual fields. The right choice begins with exact hash semantics, not the slogan “Redis hashes are objects.” A Redis hash is one Redis key whose value is a flat map of field names to String values. Redis does not infer an application class, validate business types, or create nested objects.
Explain a Redis hash as one key containing field/value pairs, with String field names/values and no automatic application schema enforcement.
Use HSET, HGET, HMGET, HGETALL, and HLEN while interpreting missing fields, return counts, response size, and command complexity correctly.
Distinguish partial-field updates from whole-record reads and explain why HGETALL becomes a latency/network concern as field cardinality grows.
Observe memory and internal encoding without treating a current encoding name or threshold as a stable API contract.
Carry key TTL, field TTL, ACL, persistence, replication, Cluster, Search/index, and client-retry boundaries into hash modeling decisions.
All Chapter 04 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from
Docker Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled only
because traffic stays on loopback, default ACL user disabled,
named ACL users atlasmart-app and
academy-admin, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data volume, and no explicit Redis
maxmemory limit or eviction policy. The
application ACL is restricted to ~atlasmart:* and
normal read/write/connection categories. The primary interface
is the redis-cli shipped in the same 8.10.1
image, so server and CLI versions stay aligned. Redis 8.10
adds newer hash capabilities such as field-expiration helpers
and HIMPORT/fieldset-related commands, but this lesson keeps
its contract to the published HSET/HGET/HMGET/HGETALL title
and treats internal encodings as observations only.
1. One key, many fields: the smallest useful hash model
The key atlasmart:product:1001 can hold a flat
collection of fields. HSET creates the hash if the
key is absent and creates or overwrites the specified fields.
Its integer reply counts new fields added, not fields
successfully processed. Updating an existing field therefore
returns zero for that field even though the value changed. That
is an important observability detail when code incorrectly
treats a zero return as failure.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:product:1001 sku SKU-1001 name "Trail Bottle" category outdoors price_cents 2499 stock_label in-stock schema_version 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:product:1001 stock_label low-stockdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HLEN atlasmart:ch04:product:1001# Expected shape:# (integer) 6 <- six new fields# (integer) 0 <- field existed; value was still updated# (integer) 6
A hash is object-like because an application can map
fields to properties, but Redis does not know that
price_cents should be an integer, that
category belongs to an enum, or that
schema_version is mandatory. Those are application
contracts unless additional validation is implemented elsewhere.
2. HGET and HMGET preserve the difference between “missing” and empty
HGET retrieves one field in O(1) expected command
complexity. A missing field or missing hash key produces a
null/nil reply. HMGET retrieves selected fields in
request order and returns a null position for each missing
field. This is not the same as an empty String: an application
that decodes both to the same host-language value can erase
useful state.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch04:product:1001 namedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch04:product:1001 promotion_codedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HMGET atlasmart:ch04:product:1001 sku name price_cents promotion_codedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HMGET atlasmart:ch04:missing sku name# Expected shape:# "Trail Bottle"# (nil)# 1) "SKU-1001" 2) "Trail Bottle" 3) "2499" 4) (nil)# 1) (nil) 2) (nil)
Fields are still binary-safe Strings. The application may parse
price_cents as an integer, but Redis will happily
accept price_cents = "unknown" through ordinary
HSET. Numeric hash commands introduced in Lesson 2
enforce numeric parsing only for the field they operate on.
3. HGETALL is complete—and O(N)
HGETALL returns every field and value, so the reply
contains twice as many scalar items as the field count. Its
complexity is O(N) in the number of fields and current command
metadata classifies it as a slow-category read. That does not
mean every small HGETALL is bad. It means record size is part of
the API contract. If one hash grows from 12 fields to 500,000
fields, a once-cheap request can become a large server
traversal, response allocation, network transfer, client parse,
and garbage-collection event.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HLEN atlasmart:ch04:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HMGET atlasmart:ch04:product:1001 sku name price_centsdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGETALL atlasmart:ch04:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO HGETALL HMGET HGET HSET HLEN# Read COMMAND INFO for current complexity/categories rather than memorizing an old cheat sheet.
A team calls HGETALL for every product card because the first test hash has six fields. The bug is not that HGETALL exists; it is that the API has no field-cardinality budget. Repair it by projecting required fields with HGET/HMGET for hot paths, measuring response bytes and tail latency, and reserving HGETALL for bounded records or administrative inspection.
4. Key TTL and field TTL are separate lifecycle layers
Chapter 02 established key-level expiration. A hash key can
still have one key TTL through EXPIRE. Redis 7.4
added expiration for individual hash fields. These are different
clocks: deleting the key removes every field; expiring one field
removes only that field. Ordinary HSET overwriting
a field clears that field's field-level expiration, while
operations that conceptually mutate the existing field value,
such as numeric increments, leave the field TTL intact. Lesson 4
proves these rules in detail.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app EXPIRE atlasmart:ch04:product:1001 3600docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:product:1001 120 FIELDS 1 stock_labeldocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch04:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:product:1001 FIELDS 2 stock_label name# Expected shape shortly after execution:# 1# 1# about 3600# about 120, then -1 for name (field exists but has no field TTL)
Do not infer tenant isolation from either mechanism. ACLs (Access Control Lists) authorize commands and key patterns; they do not become field-level application authorization simply because hashes have fields.
5. Memory evidence is useful; encoding folklore is not
Redis may use different internal encodings for small versus
large hashes, and Redis 8.10 continues to evolve hash internals.
OBJECT ENCODING is diagnostic evidence about the
current server, not a data-model guarantee.
MEMORY USAGE estimates bytes attributed to the
key/value object on that build and allocator. Neither tells you
the total process resident set size (RSS), replication buffers,
AOF buffers, Search indexes, fork copy-on-write headroom, or
client memory.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin OBJECT ENCODING atlasmart:ch04:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO serverdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO memory# Keep the observed encoding name in the lab notes, but do not design around it as an API.
The useful engineering question is not “which encoding is fastest in a blog post?” It is “what is this record's field count/value-size distribution, how does it behave on the pinned version, and what happens to p95/p99 latency and memory as it grows?”
6. Wrong type, schema drift, and update semantics
If the key holds a non-hash value, hash commands return a
WRONGTYPE error. That protects the Redis type boundary, not the
application schema. Inside a valid hash, Redis will not stop one
producer from storing schema_version=banana. Schema
evolution therefore needs explicit versioning, validation, and
deployment compatibility.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch04:wrongtype "not-a-hash"docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch04:wrongtype name# Expected: WRONGTYPE ...docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:product:1001 price_cents unknowndocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch04:product:1001 price_cents# Expected: "unknown" -- ordinary HSET does not enforce integer schema.# Repair fixture:docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:product:1001 price_cents 2499 schema_version 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:wrongtype
That failure is educational because it separates two invariants: Redis enforces the key's data type, while the application must enforce the meaning and allowed representation of individual fields.
7. Hands-on lab: define a bounded AtlasMart hash contract
Create a product-card hash whose hot read uses a three-field
projection. Record field count, whole-record response shape, key
TTL, one temporary field TTL, current encoding, and memory
usage. Then overwrite the temporary field and confirm its field
TTL is cleared. This is a disposable fixture under the
atlasmart:ch04: prefix only.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:lab:productdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:lab:product sku LAB-1 name "Lab Mug" price_cents 1599 category kitchen stock_label in-stock schema_version 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app EXPIRE atlasmart:ch04:lab:product 1800docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:lab:product 60 FIELDS 1 stock_labeldocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HMGET atlasmart:ch04:lab:product sku name price_centsdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HLEN atlasmart:ch04:lab:productdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:lab:productdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin OBJECT ENCODING atlasmart:ch04:lab:productdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:lab:product FIELDS 1 stock_labeldocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:lab:product stock_label backorderdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:lab:product FIELDS 1 stock_label# Final HTTL should be -1 because HSET overwrote the expiring field.
Verification checklist:
- The hash contains exactly six fields and HMGET returns only the requested three in request order.
- Key TTL is positive while the temporary stock_label field initially has its own positive field TTL.
- Overwriting stock_label with HSET clears only that field expiration; the key TTL remains positive.
- You record MEMORY USAGE/OBJECT ENCODING as environment evidence rather than universal constants.
- Cleanup deletes only atlasmart:ch04:lab:product and the lesson fixtures.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:lab:product atlasmart:ch04:product:1001 atlasmart:ch04:wrongtype
8. Production judgment
Hashes fit flat records when callers commonly read or mutate individual fields and the field set remains operationally bounded. The attractive O(1) HGET/HSET operations do not make a huge unbounded hash free: whole-record commands are O(N), large replies consume network/client memory, persistence and replication must propagate writes, and one hot hash key can concentrate traffic on one Cluster slot. Search can index selected hash fields, but the index adds memory and write work; a hash itself does not create secondary-query capability.
On retries, distinguish idempotent assignments from increments.
HSET of the same deterministic value is naturally repeatable;
numeric increments are not. Under replication or failover, later
chapters will measure acknowledged-write bounds rather than
assuming the standalone result implies durability. For security,
ACL key patterns can restrict atlasmart:* keys but
do not authorize one hash field differently from another.
9. Summary and next step
A Redis hash is one key containing flat field/value pairs. HSET creates or replaces fields, HGET/HMGET project selected fields, HGETALL retrieves the whole record at O(N), and HLEN exposes field cardinality. Missing fields are null, not empty strings. Key TTL and field TTL are independent lifecycle layers, and internal encoding is version-sensitive implementation evidence. Next, you will use numeric hash commands to update individual fields atomically without turning Redis Strings into a strongly typed financial database.
Check your understanding
- Why can HSET return 0 even when the value changed?
- Why is HMGET safer than HGETALL for a hot endpoint that needs only three fields?
- Does a hash enforce that price_cents is an integer?
- What happens to an expiring hash field when HSET overwrites that field?
- Why should OBJECT ENCODING not drive a permanent schema contract?
Review the answers
1. The integer reply counts newly added fields. Updating an existing field can change its value while contributing zero new fields.
2. It bounds requested fields and reply size to the projection instead of transferring every field in the record.
3. No. Ordinary HSET accepts String bytes. Numeric commands enforce numeric parsing only when invoked.
4. Its field-level expiration is cleared; this is separate from the hash key's key-level TTL.
5. Internal encodings and thresholds are implementation/version details; observe and benchmark them on the pinned server instead of treating them as API guarantees.
Authoritative references
- Redis hashes — hash data model and current command family
- HSET — field creation/overwrite and return semantics
- HGET — single-field retrieval and missing-field behavior
- HMGET — projection order, null entries, and complexity
- HGETALL — whole-hash retrieval and O(N) cost
- HEXPIRE — field expiration and overwrite-clears-expiration rule
- Redis 8.10 release notes — current pinned server baseline