jonnify·redis /redis-patterns/caching/session-management/encodings

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_store/lib/echo_store/table.ex (put/3, the framed version<>value write) — the encoding the store holds.

Same record, three shapes. Select an encoding to read its commands and what a single-field update costs.
session:abc123 Hashnamed fields Stringone blob JSONnested blob a Hash exposes each field on its own; a blob is read and written whole
readout · Select an encoding. The record is the same everywhere; the Redis type that holds it is the access-pattern choice.
§1

One record, three encodings

source · Hash / String / JSON

The 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.

§2

The cost of a single-field update

source · Memory Optimization

Sliding 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.

Update one field — Hash vs blob. Choose the field, the encoding, and the record size; the readout names the commands and the bytes moved.
256

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.

§3

The session record on EchoStore

table.ex:290, :429 · verbatim

EchoStore stores its values in the String shape, with one addition: it frames the bytes with a 14-byte branded version prefix. EchoStore.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 EchoStore 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.

# EchoStore.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>>
the application · the framed write, value and TTL in one command

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:

bcs.4.md · the L2 row, written with SET … PXfrozen
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
the pattern

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.

its EchoStore application

EchoStore 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; EchoStore 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.

§4

Recap — shape follows access

encode → expire

A 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; EchoStore 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.

§5

References

sources · related

Sources

  • 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 encoding EchoStore.Table.put writes.
  • Redis — Documentation — the Hash and String types and when each fits a record.

Related in this course