Chapter 04 · Hashes and Object-Like Records
Field-Level Expiration Capabilities Where Available and Version-Aware Client Design
Use Redis 7.4+ hash-field expiration and Redis 8.0+ HGETEX/HSETEX deliberately, verify server/client capability, and distinguish field TTL from key TTL and cache policy.
Learning outcomes
AtlasMart stores a customer session profile in one hash. Stable
fields such as customer_id and
locale should persist with the record, while fields
such as otp_verified, shipping_quote,
and a temporary recommendation token should expire
independently. Redis 7.4 introduced hash-field expiration, and
Redis 8.0 added convenience commands that combine hash
reads/writes with field expiration. These features are powerful
precisely because they are version-sensitive and have different
mutation rules from key-level TTL.
Verify server command availability before using field-expiration features and distinguish Redis 7.4 versus 8.0 command additions.
Use HEXPIRE/HPEXPIRE, HTTL/HPTTL, HPERSIST, and conditional NX/XX/GT/LT semantics with correct per-field return codes.
Use Redis 8.0+ HGETEX/HSETEX deliberately and explain how HSET overwrite, numeric mutation, KEEPTTL, and key TTL interact with field TTL.
Design clients that capability-check server and library support rather than assuming a wrapper method exists because a server version string looks new enough.
Connect field expiration to Search visibility, persistence/replication, ACL/TLS, failover, privacy, and cache-freshness policy without treating TTL as exact scheduling.
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. The mandatory
path uses redis-cli only. An optional redis-py 8.0.1 snippet
demonstrates client capability checks; no Python package is
required to complete the chapter.
1. Prove the feature surface before relying on it
Field expiration is not a timeless Redis assumption.
HEXPIRE, HPEXPIRE, HTTL,
HPTTL, and HPERSIST are available from
Redis 7.4. Redis 8.0 adds HGETEX,
HSETEX, and HGETDEL. A robust client
can ask the server for command metadata rather than merely parse
a marketing/product label.
docker 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 COMMAND INFO HEXPIRE HPEXPIRE HTTL HPTTL HPERSIST HGETEX HSETEX HGETDEL# On the pinned Redis Open Source 8.10.1 lab these commands should be present.# On an older/proxy/compatible service, missing command metadata is a compatibility signal.
Managed services and Redis-compatible proxies can expose a subset or altered rollout schedule. Application startup should fail safely or choose a tested fallback when a required command is unavailable.
2. HEXPIRE/HTTL: expiration belongs to individual fields
HEXPIRE key seconds FIELDS n field... applies
relative TTLs to selected fields. HPEXPIRE uses
milliseconds. For each requested field,
HEXPIRE/HPEXPIRE return
-2 when the key/field is missing,
0 when an NX/XX/GT/LT condition rejects the update,
1 when expiration is set or updated, and
2 when an immediate/past expiration deletes the
field. HTTL/HPTTL separately return a
positive remaining TTL, -1 for an existing
persistent field, or -2 when the key/field is
missing. Expiration is not an exact scheduler; field deletion
becomes observable according to Redis expiration processing and
access behavior, not a promise of execution at an exact
millisecond.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:session:42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:session:42 customer_id 42 locale en otp_verified 1 shipping_quote 12.50docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 30 FIELDS 1 otp_verifieddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HPEXPIRE atlasmart:ch04:session:42 45000 FIELDS 1 shipping_quotedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 3 customer_id otp_verified missingdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HPTTL atlasmart:ch04:session:42 FIELDS 1 shipping_quote# Expected shapes: customer_id -> -1; otp_verified -> positive; missing -> -2; quote -> positive ms.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HPERSIST atlasmart:ch04:session:42 FIELDS 2 otp_verified customer_iddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 2 otp_verified customer_id# HPERSIST returns 1 when it removes a field TTL and -1 when the field was already persistent.
HPERSIST removes a field expiration without
changing the value. Its per-field result is 1 when
an expiration was removed, -1 when an existing
field was already persistent, and -2 for a missing
key/field. The following HTTL should therefore
report -1 for both surviving fields.
3. Conditional field expiry: NX, XX, GT, and LT
The conditional modes mirror lifecycle intent rather than value
comparison. NX sets expiration only when the field
has no expiration. XX requires an existing
expiration. GT accepts a new expiration only when
it is greater than the current one, and LT only
when it is less. These conditions operate per field and return a
result for each requested field.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:session:42 promo_banner springdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 120 NX FIELDS 1 promo_bannerdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 60 NX FIELDS 1 promo_bannerdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 180 GT FIELDS 1 promo_bannerdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 300 LT FIELDS 1 promo_bannerdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 1 promo_banner# Read each integer result; do not treat a rejected condition as a transport error.
A client that discards per-field return codes can falsely report that all requested expirations were applied.
4. HSET clears field TTL; increments preserve it
The mutation rule is precise: commands that delete or overwrite
a hash field's contents, including HDEL and
ordinary HSET, clear that field's expiration.
Operations that conceptually modify the value without replacing
the field, such as HINCRBY, leave the field TTL
intact. The hash key's own key-level TTL is a separate property
and is not the same timer.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:session:42 refresh_count 0 temporary_note originaldocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 120 FIELDS 2 refresh_count temporary_notedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HINCRBY atlasmart:ch04:session:42 refresh_count 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:session:42 temporary_note replaceddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 2 refresh_count temporary_note# refresh_count -> positive TTL; temporary_note -> -1 after HSET overwrite.
This difference should be tested in application code because “any write refreshes TTL” and “any write clears TTL” are both incorrect generalizations.
5. Redis 8.0+: HSETEX and HGETEX combine data access with expiry policy
HSETEX sets one or more fields and can assign
EX/PX/EXAT/PXAT expiration, preserve existing field TTL with
KEEPTTL, or condition the multi-field set with
FNX/FXX. HGETEX retrieves
fields and can set a new expiration or make them persistent.
These commands reduce round trips and race windows, but their
exact option semantics are the contract—not a generic “sliding
session” guarantee.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSETEX atlasmart:ch04:session:42 EX 90 FIELDS 2 shipping_quote 13.25 recommendation_token rec-abcdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 2 shipping_quote recommendation_tokendocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSETEX atlasmart:ch04:session:42 FXX KEEPTTL FIELDS 1 shipping_quote 13.50docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 1 shipping_quotedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGETEX atlasmart:ch04:session:42 EX 30 FIELDS 1 recommendation_tokendocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 1 recommendation_tokendocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGETEX atlasmart:ch04:session:42 PERSIST FIELDS 1 recommendation_tokendocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 1 recommendation_token# HGETEX PERSIST returns the field value and removes that field TTL; HTTL should then be -1.
If a library does not yet expose a typed helper, do not silently emulate the operation with separate read/write/expire commands and claim identical atomicity. Either use a tested raw-command escape hatch against a capability-checked server or choose an explicit fallback with documented weaker semantics.
6. Version-aware client design: capability first, wrapper second
As of this course snapshot, redis-py 8.0.1 is a current release and Redis documentation exposes Python APIs for modern hash-expiration commands. Client releases can change response protocol defaults, method names, type annotations, and Cluster routing behavior independently of the server. Record both versions in diagnostics.
# pip install redis==8.0.1import redisr = redis.Redis(host="127.0.0.1", port=6379, db=0, username="atlasmart-app", password="AtlasMart-App-Lab-Only-2026", decode_responses=True)info = r.info("server")cmds = r.execute_command("COMMAND", "INFO", "HEXPIRE", "HTTL", "HSETEX", "HGETEX")print("server:", info.get("redis_version"))print("client:", redis.__version__)print("commands available:", [c is not None for c in cmds])# Only after capability passes should application code depend on these semantics.# Keep an integration test against the exact managed/proxy target as well.
For production, capability negotiation should be deterministic at startup/deployment rather than discovering a missing command in the middle of a customer request.
7. Expiration signals are not business-state logs
Field expiry affects authoritative Redis state; notification mechanisms are signals, not durable audit logs. If privacy policy says an OTP field must not be usable after a deadline, the application should read authoritative state and treat missing/expired data correctly. If compliance requires proof of deletion or retention, build auditable workflows beyond a best-effort notification subscriber.
Redis Search in Redis 8 has explicit behavior for expiring hash fields. Index/query visibility can therefore depend on expiration semantics and query timing; Chapter 09 measures that behavior. Do not assume a secondary index makes expired fields immortal or that a notification is a transactional record of every expiry.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:session:42 one_time 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:session:42 2 FIELDS 1 one_timedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 1 one_time# Wait >2 seconds on your machine, then read authoritative state:docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch04:session:42 one_timedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:session:42 FIELDS 1 one_time# Expect the field eventually to be absent; do not promise an exact deletion millisecond.
8. Hands-on lab: mixed-lifetime session record
Create a stable session hash with one permanent identity field, one 60-second quote, one 30-second token, and one counter whose TTL survives HINCRBY. Verify server capabilities first. Then overwrite one expiring field with HSET to demonstrate TTL clearing and repair it with HSETEX.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch04:lab:sessiondocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO HEXPIRE HTTL HSETEX HGETEXdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:lab:session customer_id 42 quote 12.00 token tok-1 refresh_count 0docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:lab:session 60 FIELDS 1 quotedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:lab:session 30 FIELDS 1 tokendocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HEXPIRE atlasmart:ch04:lab:session 120 FIELDS 1 refresh_countdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HINCRBY atlasmart:ch04:lab:session refresh_count 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch04:lab:session quote 12.50docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:lab:session FIELDS 4 customer_id quote token refresh_countdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSETEX atlasmart:ch04:lab:session EX 60 FIELDS 1 quote 12.50docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HTTL atlasmart:ch04:lab:session FIELDS 1 quote
Verification checklist:
- COMMAND INFO proves the required commands exist on the actual server before the lab depends on them.
- customer_id reports -1 field TTL, while token/refresh_count/quote report positive TTLs at the relevant steps.
- HINCRBY preserves refresh_count TTL; HSET clears quote field TTL; HSETEX restores quote with an explicit TTL.
- The lab distinguishes field TTL from any key-level TTL and does not treat expiry as exact scheduling.
- Client/version assumptions are recorded; managed/proxy targets require their own integration test.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch04:lab:session atlasmart:ch04:session:42
9. Production judgment
Field TTL is appropriate when one flat hash record contains values with legitimately different lifetimes and the feature is supported/tested across your exact server, client, persistence, replication, and managed-service topology. It can reduce many-key cardinality while retaining per-field lifecycle control. It also increases lifecycle complexity: every mutation path must know whether it clears, preserves, shortens, extends, or removes the field TTL.
Monitor command errors, expired-field rates where exposed, hash cardinality, memory, stale-value incidents, and client capability mismatches. Test failover and restore behavior before relying on expiration for correctness-sensitive workflows. TTL is not encryption, authorization, or proof of deletion; ACL/TLS/network boundaries and application authorization remain separate controls.
10. Summary and next step
Redis 7.4 introduced field-level expiration and Redis 8.0 added HGETEX/HSETEX/HGETDEL convenience commands. TTL is per field, return codes are per field, HSET overwrite clears field expiry, numeric mutation preserves it, and key TTL remains a separate lifecycle layer. Capability-check the server and client instead of assuming version parity. Next, you will decide when this flat hash model is preferable to Redis JSON or many independent keys.
Check your understanding
- What does HTTL return for an existing field without field expiration?
- What does HTTL return for a missing field or missing hash key?
- What happens to a field TTL when ordinary HSET overwrites that field?
- Why is HSETEX different from HSET followed by HEXPIRE?
- Why should an application call COMMAND INFO during compatibility checks?
Review the answers
1. -1.
2. -2 for that requested field.
3. The field expiration is cleared.
4. HSETEX combines value setting and expiration policy in one command with its documented atomic command semantics, avoiding the inter-command race/window.
5. It verifies the actual target exposes required commands; server/client/product labels alone may not prove feature availability on proxies or managed services.
Authoritative references
- HEXPIRE — Redis 7.4 field expiration, conditions, and mutation rules
- HPTTL — per-field millisecond TTL return semantics
- HSETEX — Redis 8.0 set-with-field-expiration and KEEPTTL/FNX/FXX
- Redis 8.0 changes — HGETEX/HSETEX/HGETDEL introduction and Redis 8 hash/search context
- Redis hashes — current hash command family
- redis-py releases — client release/version context
- Key and field expiration with Search — Redis Search interaction with key/field expiry