Chapter 08 · Redis JSON and Document-Oriented Data

Document Modeling: Embedding, Duplication, Key Design, Size, and Partial Updates

Model bounded Redis JSON documents with explicit embedding, duplication, schema-version, key-lifecycle, memory, and migration tradeoffs.

Intermediate155–190 minutesDocument modeling and schema-version labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart can now store and mutate nested documents, but document shape is an architectural decision. Embedding everything into one product or customer document can create giant hot keys; splitting every property into a Redis key can create cardinality and lifecycle problems. This lesson builds a deliberate middle ground.

01

Choose embedding versus references/duplication from update and read patterns.

02

Design stable Redis keys and explicit schema-version fields for JSON documents.

03

Bound arrays and nested objects so documents do not grow invisibly forever.

04

Use partial updates while preserving source-of-truth and duplication rules.

05

Measure document memory/payload characteristics instead of declaring JSON or Hash universally better.

Exact lab baseline

All Chapter 08 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. Redis 8 integrates JSON and Search capabilities in Redis Open Source, so the pinned image is the mandatory free/local path; no separate Redis Stack image or managed service is required. The primary client is the redis-cli shipped in the same image. Fixtures are bounded under atlasmart:ch08:*.

1. Model around access and change boundaries

Embedding is useful when sub-data is read with the parent, updated under the same lifecycle, and has bounded size. Separate keys are useful when sub-data has independent lifecycle, very different access frequency, separate authorization, or potentially unbounded cardinality.

AtlasMart data Likely shape Why
product dimensions embed in product JSON small, bounded, usually read with product
recent product badges bounded embedded array small display-oriented collection with explicit cap
all product reviews separate review keys/stream/index unbounded growth and independent lifecycle
inventory per warehouse depends on update/hotness can embed when bounded; separate/shard when high-frequency or independently authorized
customer payment secrets do not casually embed security and access boundary is stronger than convenience

2. Stable key naming stays outside the JSON schema

The Redis key locates the document operationally; fields inside the document describe business data. Keep identity derivable and avoid putting volatile attributes into the key.

redis-cli · explicit key and identity fixture
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:product:2001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:2001 '$' '{"schemaVersion":1,"productId":2001,"sku":"PACK-2001","name":"City Pack","badges":["new"],"supplier":{"id":88,"displayName":"Northwind"}}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:2001 '$.productId' '$.sku'

The duplicated product ID inside the document can help validation/debugging, but the application must define which identity is authoritative if they ever disagree.

3. Duplication is a consistency contract

AtlasMart may duplicate supplier display name into a product read model to avoid another lookup. That is not inherently wrong, but it creates a synchronization rule: when the supplier name changes, old product snapshots can become stale.

Duplication choice Benefit Cost / repair mechanism
no duplication single authoritative value extra lookup or composition
duplicate display field fast local read event/update fan-out plus reconciliation
duplicate immutable ID only stable reference still needs lookup for current display fields

Document databases do not erase distributed consistency; they move it into modeling choices.

4. Schema version is explicit application data

Redis JSON accepts valid JSON, not “AtlasMart Product v2.” Put an explicit version in the document and make readers/writers define supported versions.

redis-cli · mixed schema fixtures
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:schema:v1 atlasmart:ch08:schema:v2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:schema:v1 '$' '{"schemaVersion":1,"name":"Pack","priceCents":5000}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:schema:v2 '$' '{"schemaVersion":2,"name":"Pack","pricing":{"amountCents":5000,"currency":"AZN"}}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:schema:v1 '$.schemaVersion' '$.priceCents'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:schema:v2 '$.schemaVersion' '$.pricing.amountCents'

A v2 Search schema or client class does not transform v1 automatically. Migration is a data operation with rollback and compatibility strategy.

5. Additive evolution is usually easier than destructive renaming

A staged migration can first teach readers both shapes, then write the new shape, backfill old documents, switch readers, and only later remove the old field. This makes rollback possible during the compatibility window.

redis-cli · bounded v1 to v2 backfill example
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:schema:v1 '$.pricing' '{"amountCents":5000,"currency":"AZN"}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:schema:v1 '$.schemaVersion' '2'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:schema:v1 '$'

In a real migration, validate that the old amount and new amount agree before deleting the old path, and record counts/errors rather than assuming every document is conformant.

6. Bound arrays explicitly

Embedded arrays are attractive because they serialize naturally, but an unbounded reviews/history array turns one key into a large mutable object. Keep only the bounded subset that belongs in the parent view.

redis-cli · bounded recent-badges pattern
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRAPPEND atlasmart:ch08:product:2001 '$.badges' '"featured"' '"weekend"' '"clearance"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRLEN atlasmart:ch08:product:2001 '$.badges'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRTRIM atlasmart:ch08:product:2001 '$.badges' -3 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:2001 '$.badges'

The trim is a policy choice, not a magic production number. Pick caps from product semantics, payload/memory data, and update frequency.

7. Giant-document failure mode

A practitioner may embed every customer order, event, review, and click inside one customer JSON because nested arrays are convenient. The result is a hot, ever-growing key whose reads, persistence copy-on-write impact, replication traffic, backup size, and mutation latency can all worsen.

Safer repair

Split unbounded histories into their own structures—Streams, Sorted Sets, review documents, or indexed records—while the customer JSON keeps only bounded summary/current-state fields. Verify the relationship with IDs and reconciliation.

8. Partial updates reduce payload, not logical coupling

A narrow write such as updating $.supplier.displayName avoids root replacement, but the whole document is still one Redis key for ACL key-pattern purposes, key expiration, persistence, replication, eviction, and Cluster routing.

redis-cli · narrow update plus key-level evidence
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:2001 '$.supplier.displayName' '"Northwind Trading"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:2001 '$.supplier'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch08:product:2001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:product:2001

9. TTL is key-level lifecycle

Redis key expiration applies to the entire JSON key. Do not assume nested members independently expire because Hash field-expiration exists elsewhere in Redis. If one embedded fragment needs a radically shorter lifecycle, that is a modeling signal to consider a separate key or explicit application cleanup.

redis-cli · prove whole-document TTL
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app EXPIRE atlasmart:ch08:product:2001 300docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch08:product:2001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:2001 '$.name' '"City Pack 2"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch08:product:2001

A path mutation does not create a field TTL. Validate exact key-TTL preservation rules in your target release/client whenever lifecycle behavior is business-critical.

10. Security boundaries are not JSON paths

The Chapter 01 ACL user is scoped to atlasmart:* keys and command categories. That does not mean Redis ACLs automatically authorize “this caller can see $.public but not $.private” inside one JSON document. If sub-document fields have different authorization requirements, application-layer filtering or separate keys/services may be safer.

11. Compare memory with equivalent bounded representations

Build the same tiny record as JSON and Hash and observe, without turning one sample into a universal rule.

redis-cli · bounded JSON versus Hash fixture
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:compare:json atlasmart:ch08:compare:hashdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:compare:json '$' '{"sku":"A-1","name":"Adapter","stock":7}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch08:compare:hash sku A-1 name Adapter stock 7docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:compare:jsondocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:compare:hash

Repeat with realistic field counts/value sizes and enough keys before drawing a capacity conclusion. Redis internal encodings and allocator behavior are version/data dependent.

12. Migration must preserve observability and rollback

For an online schema migration, record at least: source document count, version distribution, conversion successes/failures, old/new field agreement, Search index coverage if relevant, memory delta, latency distribution, and rollback window. A migration script that “ran without exception” is not sufficient evidence.

13. Reproducible modeling lab

Create one intentionally bounded product, mutate a duplicated read-model field, and prove schema/version/memory state.

redis-cli · modeling acceptance path
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:lesson3docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:lesson3 '$' '{"schemaVersion":2,"productId":3001,"name":"Bottle","supplier":{"id":9,"displayName":"Aqua"},"recentLabels":["new","eco"]}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:lesson3 '$.supplier.displayName' '"Aqua Co"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRAPPEND atlasmart:ch08:lesson3 '$.recentLabels' '"featured"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRTRIM atlasmart:ch08:lesson3 '$.recentLabels' -3 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:lesson3 '$.schemaVersion' '$.supplier' '$.recentLabels'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:lesson3docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:lesson3

14. Production judgment

Choose document boundaries from lifecycle, authorization, cardinality, update hotness, read composition, and failure recovery. Embedded JSON is excellent for bounded cohesive records, but it can become a hot/big key when used as an unlimited aggregate. Keep schema versions explicit, migrations observable, duplicates reconcilable, and TTL/security boundaries aligned with key boundaries.

15. Summary and next step

You can now model JSON documents without defaulting to “embed everything” or “split everything.” Next, we add a deliberately scoped Redis Search index and keep its schema/migration state distinct from stored JSON.

Check your understanding

  1. When is embedding usually attractive?
  2. Why is duplicated supplier displayName a consistency contract?
  3. Does a JSON path have its own Redis key TTL?
  4. Why is one small MEMORY USAGE comparison insufficient to choose Hash versus JSON?
  5. What should an online schema migration measure?
Review the answers

When sub-data is bounded, shares lifecycle/authorization, and is commonly read with the parent.

The duplicate can become stale and needs an update/reconciliation rule.

No. Redis expiration applies to the containing key.

Internal encodings, value sizes, field counts, allocator behavior, and workload shape can reverse the result.

Version distribution, conversion success/failure, old/new agreement, index coverage when relevant, memory/latency changes, and rollback state.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.