Chapter 04 · Hashes and Object-Like Records
Hashes vs JSON vs Many Keys: Memory, Partial Updates, Indexing, and Schema Tradeoffs
Choose hashes, Redis JSON, or many keys from structure, update/query patterns, expiration granularity, memory/cardinality, indexing, ACL boundaries, and migration evidence.
Learning outcomes
AtlasMart now has three competing models for a product/customer
read model: one Redis hash per entity, one Redis JSON document
per entity, or many independent keys such as
...:name, ...:price, and
...:stock. None is universally “best.” The choice
changes nesting, types, partial updates, expiry granularity, key
cardinality, ACL boundaries, Cluster placement, Search indexing,
network shape, migration cost, and operational discoverability.
Compare hashes, Redis JSON, and many keys using structure, type semantics, partial updates, query/index requirements, TTL granularity, memory/cardinality, and client ergonomics.
Build equivalent AtlasMart fixtures and measure MEMORY USAGE/key cardinality without turning one local measurement into a universal rule.
Explain Redis Search as a secondary indexing layer for hash or JSON documents rather than an automatic property of either data type.
Identify security/tenant boundaries: ACLs primarily govern commands and key patterns, not arbitrary hash/JSON subfields as independent security principals.
Design an evidence-based migration/rollback plan instead of changing representations solely for theoretical memory savings.
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 Open
Source 8 integrates JSON and Search capabilities that older
Redis Stack tutorials treated as separate packaging. The lab
capability-checks JSON.SET before using it; if a target
service lacks JSON, the mandatory comparison can still be
completed with hash versus many keys and the JSON sections
remain conceptual.
1. Hash: flat record, field operations, one key identity
A hash stores flat field/value pairs under one key. It is strong when the application frequently reads or mutates individual top-level fields and values naturally fit String representations. Modern field expiration can give selected fields independent lifetimes. Hashes are also indexable by Redis Search when you explicitly create an index schema. They do not natively express nested arrays/objects or enforce business types.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:compare:hash:1001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:compare:hash:1001 sku SKU-1001 name "Trail Bottle" price_cents 2499 category outdoors stock 12docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HMGET atlasmart:ch04:compare:hash:1001 name price_cents stockdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HLEN atlasmart:ch04:compare:hash:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:hash:1001
2. Redis JSON: hierarchical typed document, path operations
Redis JSON stores JSON values and supports JSONPath selection/mutation. That matters when AtlasMart needs nested dimensions, arrays, booleans, numbers, or subdocuments without flattening them into field-name conventions. JSON provides richer data modeling; it still does not automatically enforce AtlasMart's domain schema or authorization rules. Redis Search can index JSON paths with an explicit index schema.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO JSON.SET JSON.GET JSON.NUMINCRBY FT.CREATE# Continue only if JSON.SET is present on the target.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:compare:json:1001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch04:compare:json:1001 '$' '{"sku":"SKU-1001","name":"Trail Bottle","price_cents":2499,"category":"outdoors","stock":12,"dimensions":{"ml":750}}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch04:compare:json:1001 '$.name' '$.price_cents' '$.dimensions.ml'docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:json:1001
The exact JSON reply formatting depends on path syntax, client, and RESP handling. The design point is hierarchical typed structure and path updates, not a prettier serialization.
3. Many keys: independent lifecycle and routing at the cost of key cardinality
Splitting fields into separate Redis keys gives each value independent key TTL, key-level ACL pattern possibilities, type choice, and potentially independent Cluster slot placement. The tradeoff is more top-level keys, longer repeated key names/metadata, more multi-key orchestration, more SCAN/discovery surface, and possible partial states when updates span keys. If Cluster co-location is required, hash tags can force related keys into one slot—but then you deliberately give up distribution for those keys.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MSET atlasmart:ch04:compare:keys:1001:sku SKU-1001 atlasmart:ch04:compare:keys:1001:name "Trail Bottle" atlasmart:ch04:compare:keys:1001:price_cents 2499 atlasmart:ch04:compare:keys:1001:category outdoors atlasmart:ch04:compare:keys:1001:stock 12docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MGET atlasmart:ch04:compare:keys:1001:name atlasmart:ch04:compare:keys:1001:price_cents atlasmart:ch04:compare:keys:1001:stockdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app EXPIRE atlasmart:ch04:compare:keys:1001:stock 120docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch04:compare:keys:1001:stockdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SCAN 0 MATCH "atlasmart:ch04:compare:keys:1001:*" COUNT 100
Do not assume MGET/MSET over arbitrary keys works unchanged in Redis Cluster: multi-key commands generally require keys to be in the same hash slot. Chapter 21 will make that routing constraint explicit.
4. Compare semantics before memory
| Dimension | Hash per entity | JSON per entity | Many keys per entity |
|---|---|---|---|
| Structure | Flat field/value map | Nested JSON objects/arrays and typed JSON values | Independent values/types per key |
| Partial update | HSET/HINCRBY field operations | JSON.SET/NUMINCRBY/path operations | SET/INCR/etc. per key |
| Whole record | HGETALL O(N fields) | JSON.GET document/path(s) | MGET if keys co-located/supported; application assembles |
| Expiration | Key TTL + modern per-hash-field TTL | Primarily key TTL for document lifecycle | Independent TTL per key |
| Cardinality | One top-level key per entity | One top-level key per entity | Many top-level keys per entity |
| Search/index | Explicit Redis Search index on hash fields | Explicit Redis Search index on JSON paths | Usually model/index explicitly; not automatic |
| Schema | Application-enforced flat contract | JSON types/nesting, but domain schema still application-enforced | Application-enforced across keys |
| ACL boundary | Key/command patterns; fields not independent principals | Key/command patterns; JSON paths not automatic security principals | Individual keys can match different ACL key patterns |
| Cluster | One entity key -> one slot | One entity key -> one slot | Can distribute or co-locate with hash tags; multi-key rules matter |
Start from required operations and correctness boundaries. Memory is important, but a representation that saves bytes while forcing unsafe client-side joins or preventing needed queries can be a net loss.
5. Measure memory and cardinality correctly
MEMORY USAGE is useful for comparing fixtures on
one pinned build, but values depend on allocator, encoding,
payload size, integrated modules, and server version. Many-key
modeling also consumes dictionary/key metadata that one hash
avoids; JSON carries structural/type representation overhead;
hashes can change internal encoding as they grow. Measure
representative distributions rather than one tiny record.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:hash:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:json:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:keys:1001:skudocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:keys:1001:namedocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:keys:1001:price_centsdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:keys:1001:categorydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch04:compare:keys:1001:stockdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin DBSIZE# Sum the five many-key results for this fixture, but label the exact build/environment.# DBSIZE is database-wide; isolate/compare prefixes carefully on a shared lab.
A serious benchmark should scale to representative entity counts/value sizes, warm up, record p50/p95/p99 latency, disclose persistence and concurrency, and include index memory if Search is part of the design. This lesson does not fabricate such numbers.
6. Indexing changes the write/read cost model
Redis Search maintains secondary index structures for matching hash or JSON documents after you explicitly create an index. An index can make text/tag/numeric/geo/vector queries possible, but it consumes memory and adds indexing work to writes. Primary key/field access remains different from secondary search. Do not choose JSON only because “it is searchable” or hash because “it is faster”; both can be Search document sources.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO FT.CREATE FT.INFO FT.SEARCH# Example schemas only; Chapter 09 builds and profiles them in depth:# FT.CREATE idx:product:hash ON HASH PREFIX 1 atlasmart:product: SCHEMA name TEXT price_cents NUMERIC category TAG# FT.CREATE idx:product:json ON JSON PREFIX 1 atlasmart:productjson: SCHEMA $.name AS name TEXT $.price_cents AS price_cents NUMERIC# Creating indexes changes memory/write behavior, so do it only in the dedicated isolated Search lab.
7. Deliberately wrong modeling shortcuts
“Put every object in one giant hash.” This reduces top-level key count but creates one hot/failure/cardinality boundary and complicates per-entity lifecycle/search. “Split every field into its own key.” This can explode key cardinality and multi-key coordination. “JSON gives schema validation.” JSON preserves JSON types/structure but does not automatically enforce AtlasMart's domain schema. “Hash is secure field isolation.” Redis ACL key patterns do not make individual hash fields separate tenant principals.
# Hash: expire one field without creating another top-level key (Redis 7.4+):docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:compare:hash:1001 120 FIELDS 1 stockdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:compare:hash:1001 FIELDS 1 stock# Many keys: stock has its own ordinary key TTL:docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch04:compare:keys:1001:stock# JSON: check document key TTL; path-level business lifecycle needs an explicit design.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch04:compare:json:1001
8. Hands-on lab: decision record with rollback path
For AtlasMart's simple product card, compare the three fixtures. Record required reads/writes, nested-structure needs, independent TTL needs, Search requirements, key cardinality, memory observations, Cluster locality, and ACL boundaries. Then choose one representation for this specific workload and write a rollback/migration plan.
Verification checklist:
- Hash, JSON (when command capability exists), and five-key representations contain semantically equivalent core product data.
- Memory values are recorded as local measurements with Redis 8.10.1, not universal rankings.
- The decision states whether nested data or Redis Search is actually required rather than assumed.
- TTL granularity and ACL/key-boundary implications are explicit.
- Cluster multi-key locality is considered before choosing many keys for an operation that needs atomic/batched multi-key access.
- Migration includes dual-read/dual-write or versioned-key strategy, validation counts/checksums, and a rollback window appropriate to the application.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch04:compare:hash:1001 atlasmart:ch04:compare:json:1001 atlasmart:ch04:compare:keys:1001:sku atlasmart:ch04:compare:keys:1001:name atlasmart:ch04:compare:keys:1001:price_cents atlasmart:ch04:compare:keys:1001:category atlasmart:ch04:compare:keys:1001:stock
9. Production judgment
Choose a hash for bounded flat records with frequent field-level access/mutation. Choose JSON when hierarchical structure, typed JSON values, JSONPath operations, or JSON-oriented client contracts materially simplify the workload. Choose many keys when independent lifecycle/type/security-key-pattern/routing boundaries justify the extra cardinality and coordination. These are workload decisions, not brand hierarchies.
Before migration, benchmark representative data and queries, include Search index memory/write amplification if relevant, test backups/restores and failover, inspect ACL/TLS/tenant boundaries, and plan rollback. A representation change can alter key names, Cluster slots, TTL behavior, client response types, index schemas, persistence size, and operational tooling; treat it as a data migration, not a refactor with zero operational risk.
10. Summary and next step
Hashes give flat field/value records with efficient partial operations and modern field TTL. JSON gives hierarchical typed JSON documents and path operations. Many keys give independent top-level lifecycle/routing boundaries at greater cardinality and coordination cost. Both hashes and JSON can be indexed by Redis Search when you explicitly build a secondary index. The right model comes from access patterns, lifecycle, query needs, memory, security, topology, and migration evidence. Chapter 05 now moves from object-like records to ordered and unordered collections with Lists and Sets.
Check your understanding
- When is a hash usually a strong fit?
- What does JSON add over a hash?
- What is the main operational cost of splitting every field into a key?
- Does choosing hash or JSON automatically create secondary indexes?
- Why is one MEMORY USAGE comparison insufficient to declare a winner?
Review the answers
1. A bounded flat record whose callers frequently read or mutate individual top-level fields.
2. Hierarchical JSON objects/arrays, typed JSON values, and JSONPath-based access/mutation.
3. Higher top-level key cardinality plus multi-key coordination/routing/metadata overhead.
4. No. Redis Search indexes are explicit secondary structures with their own memory/write costs.
5. Allocator, encoding, payload distribution, version, index presence, and representative scale all affect memory; benchmark the real workload and environment.
Authoritative references
- Compare Redis data types — official hash versus JSON structural guidance
- Redis hashes — flat field/value model and commands
- Redis JSON — hierarchical JSON storage and path operations
- Redis Search indexing — explicit indexing for JSON and hash documents
- MEMORY USAGE — per-key memory measurement caveats/context
- Redis Cluster specification — hash slots and multi-key locality
- Redis ACL — command/key-pattern authorization boundaries