R1.06.1 · Session management — dive 1
Three shapes for one record.
A Hash for field-level reads and writes. A string for one opaque blob. JSON when the blob has structure. The trade-off is field access against a single round trip.
Grounding: echo/apps/echo_cache/lib/echo_cache/table.ex (put/3, the framed version<>value write) — the encoding the store holds.
One record, three encodings
source · Hash / String / JSONThe record is the same everywhere: a handful of facts about one signed-in user. What changes is the Redis type that holds it, and that choice is driven by one question — how is the record read and written.
A Hash stores the record as named fields under the key. HSET session:abc123 user_id "1"
created_at "1706648400" last_access "1706648400" writes them; HGETALL session:abc123 reads
them all; HGET session:abc123 user_id reads one. The win is field-level access: a sliding-expiry
update writes only last_access, leaving the rest untouched and never re-sending the whole record over
the wire.
A string stores the record as one value. SET session:abc123 <serialized> PX
1800000 writes it whole; GET session:abc123 reads it whole. There are no fields: every update
is a read of the whole value, a change in the application, and a write of the whole value back. Simpler to reason
about, but a single-field change is a full round-trip rewrite.
JSON is the string case with structure inside the blob —
{"user_id":1,"cart":["item1","item2"],"prefs":{"theme":"dark"}}. It earns its keep when the session
carries nested data a flat Hash cannot model cleanly: a cart, a preferences tree. The cost is the same as the
plain string: the whole document is read and written on every change.
- Hash
- Named fields.
HSET/HGET/HGETALL. Field-level reads and writes; the natural fit for a flat session. - String
- One opaque value.
SET/GET. Read-modify-write the whole record on any change. - JSON
- A string holding a nested document. Best for sessions with structure (carts, preference trees); same whole-document read and write.
All three set the deadline the same way — EXPIRE on the Hash key, or
PX on the SET. The expiry choice is the next dive,
R1.06.2 · TTL expiry.
The cost of a single-field update
source · Memory OptimizationSliding expiry touches one field on every request: last_access moves forward. That single-field
update is where the encodings diverge. A Hash writes the one field with HSET session:abc123 last_access
<now> — one command, one field, the rest of the record untouched. A string or JSON value has no
fields: the application reads the whole value, changes last_access in memory, and writes the whole
value back — a read-modify-write, two commands, the entire record over the wire twice.
Pick a field to update and an encoding to see what the change costs. The readout names the commands and the bytes moved; a Hash touches one field, a blob moves the whole record.
Field access is the whole trade-off: a Hash updates one field in one command; a string or JSON value reads and rewrites the whole record. Flat session, pick a Hash; nested data, accept the blob.
The session record on EchoCache
table.ex:290, :429 · verbatimEchoCache stores its values in the String shape, with one addition: it frames the bytes with a 14-byte branded
version prefix. EchoCache.Table.put/3 writes SET ecc:{<table>}:<id>
(version<>value) PX ttl_ms (table.ex:290), and a read splits the frame back apart —
<<version::binary-14, value::binary>> (table.ex:429). The framed prefix is
what lets a later coherence message compare two writes newer-wins; a plain unframed SET is the
simpler choice when no cross-node coherence is needed.
A session record is flat and few-field — a SES id, a user id, an opaque token — the
shape a Hash backs cleanly when the store is bare Valkey; the framed-String form is what EchoCache itself holds.
How a credential is verified and the SES id is minted is the functional-Elixir and OTP craft of the
/elixir course; this dive is the encoding the store holds.
# EchoCache.Table.put/3 — the writer path, set both layers under the declared TTL (table.ex:290) SET ecc:{sessions}:SES0KH... <version<>value> PX 1800000 # a read splits the 14-byte version prefix back off the value (table.ex:429) GET ecc:{sessions}:SES0KH... # -> <<version::binary-14, value::binary>>
SET … PX against live Valkey
The L2 row carries its expiry on the server’s own clock, written in one command — the committed cache record against live Valkey:
the L2 row SET … PX, PTTL 300 ms of 300 L1 hit reads 1311621 / s ← 762 ns each, caller-side ETS L2 GET on the same wire 31 us ← the round trip a hit replaces
Encode the session by its access pattern: a Hash for flat field-level reads and writes, a string or JSON blob for a record read and written whole. Field access against one round trip.
EchoCache holds the framed-String form — version<>value under a branded
SES key — so a coherence message can compare writes newer-wins. The session is flat, so the
frame, not nesting, is the work.
The encoding follows the access pattern. A flat session fits a Hash on bare Valkey; EchoCache frames
it as a versioned String; a nested cart fits JSON — all end with the same PX deadline.
Notes on ValkeySET … PX sets the value and the TTL in
one command, so the framed row and its deadline land together —
valkey.io/commands/set.
Recap — shape follows access
encode → expireA session record fits three shapes, and the access pattern picks one. A Hash exposes named fields, so a
single-field update — the sliding-expiry write — costs one HSET. A string or JSON value
is opaque, so any change is a read-modify-write of the whole record. Flat sessions take the Hash; nested data
takes JSON; EchoCache frames its bytes as a versioned String. The deadline is set the same way for all of them.
The next dive is that deadline: how a TTL is set, slid, and reclaimed.
References
sources · relatedSources
- Redis — HSET — set named fields of a Hash; the field-level write a Hash session uses.
- Redis — HGETALL — read every field of a Hash in one command; the full-session read.
- Valkey — SET — set a key to one value with
PX; the framed-String encodingEchoCache.Table.putwrites. - Redis — Documentation — the Hash and String types and when each fits a record.
Related in this course
- R1.06 · Session managementthe module hub
- R1.06.2 · TTL expirythe next dive
- R1 · Cachingthe chapter
- R0 · OverviewValkey under the Exchange Platform
- /echomqthe protocol the coherence lane rides