R1.06.2 · Session management — dive 2
A deadline that slides.
An absolute TTL ends the session at a fixed time from sign-in. A sliding TTL refreshes the deadline on each request, so active users stay in and idle ones fall out.
Grounding: echo/apps/echo_cache/lib/echo_cache/table.ex (SET…PX, the jittered expires_at/1, the :sweep handler) — the deadline the store enforces.
Fixed at sign-in, or moved on access
source · Absolute vs slidingA session usually should expire after a period of inactivity, not at a fixed time after sign-in. A learner reading a long page for forty minutes should not be logged out mid-read because their session was born forty-one minutes ago. That is the case for a sliding deadline.
An absolute deadline is one EXPIRE session:abc123 1800 at sign-in. The key dies
thirty minutes later regardless of activity. It is the right choice when a hard session cap matters — a
banking session that must end on a fixed schedule whatever the user does.
A sliding deadline runs the same EXPIRE session:abc123 1800 after every read.
HGETALL to read the record, then EXPIRE to push the deadline a full window past now.
While requests keep arriving inside the window, the deadline keeps moving and the session stays alive. The moment
requests stop, the last EXPIRE stands, and the key dies one window after the final request. Active
users stay in; idle ones fall out.
- absolute
- One
EXPIREat sign-in. The session ends at a fixed instant, busy or not. A hard cap. - sliding
- An
EXPIREafter each read, resetting the deadline to a full window past now. Idle-based expiry. - TTL key
- Read the remaining time-to-live in seconds;
-2means the key is gone,-1means it has no deadline.
A sliding deadline reset by an EXPIRE on each read is exactly what the source calls a
“sliding window”: each access pays one extra command and buys another full window of life.
How a key is actually reclaimed
source · Session Data CleanupA deadline is a promise, not an instant deletion. When a key’s TTL passes, the engine does not stop the world to delete it that second. It reclaims an expired key two ways, and knowing both is what makes a TTL real.
Lazy eviction happens on access: when a command touches a key whose TTL has passed, the engine
detects the lapsed deadline, deletes the key, and responds as if it were absent — a GET returns
nil, an HGETALL returns empty. The expired key is reclaimed exactly when it is next asked for.
Active eviction runs in the background: the engine periodically samples keys that carry a TTL and
deletes the ones that have expired, so a key that is never read again does not sit in memory forever.
For a session this matters in one way: an expired session is functionally gone the instant a request reads it, because the read lazily evicts it and returns nothing. The application never receives a stale session. The active sweep is what keeps abandoned sessions — the ones no request ever returns to — from leaking memory. Pick a key state below to see which mechanism reclaims it and what the next read returns.
A TTL is reclaimed lazily on the next read and actively by a background sweep. An expired session is gone the instant it is read; the sweep keeps the never-read ones from leaking.
The deadline on EchoCache
table.ex:290, :484, :350 · verbatimEchoCache writes its L2 row with the TTL in the same command — SET ecc:{<table>}:<id>
(version<>value) PX ttl_ms (table.ex:290) — so value and deadline land atomically;
a session is a TTL’d row and the PX expiry is its lifetime. A re-put on the next
request re-stamps the row with a fresh deadline, the sliding-window move.
Above L2, the L1 ETS copy carries its own expiry drawn from a jittered clock —
expires_at/1 returns ttl ± ttl·jitter (table.ex:484) — so a
cohort filled together never expires together, and a separate :sweep handler
(table.ex:350) reclaims the dead rows on a fixed tick with one :ets.select_delete. Two
clocks, each layer sovereign over its own staleness. The functional-Elixir and OTP craft behind the cache is the
/elixir course; this dive is the deadline the store enforces.
# EchoCache.Table — L2 row written with its expiry, value and deadline in one command (table.ex:290) SET ecc:{sessions}:SES0KH... <version<>value> PX 1800000 # L1 expiry is jittered, so a cohort filled together never expires together (table.ex:484) expires_at = monotonic_now + ttl_ms ± ttl_ms·jitter # the :sweep handler reclaims the dead L1 rows on a fixed tick (table.ex:350) :ets.select_delete(table, [expired rows])
The jittered expiry, gated
Rows filled together must not expire together, or the TTL itself schedules the next herd — the committed cache record shows the jitter spreading a cohort:
400 rows filled in 24 ms expire 138 ms apart at jitter 0.2 ← no synchronized re-herd
the sweeper swept 400, table size 0
the L2 row SET … PX, PTTL 300 ms of 300
Choose the deadline by policy: an absolute EXPIRE at sign-in for a hard cap, or a sliding
EXPIRE on each read for idle-based expiry. The engine reclaims the key lazily on read and actively
in the background.
The L2 row carries PX on the server’s clock; the L1 copy carries a jittered
expires_at and a :sweep handler reclaims the dead rows — two clocks, neither
trusting the other’s watch.
A sliding deadline keeps active sessions alive and lets idle ones lapse; lazy eviction makes the lapse certain the instant the session is next read, and the jittered clock keeps a cohort from expiring together.
Notes on ValkeyEXPIRE does not delete at the deadline instant:
the engine reclaims a TTL’d key on access and on an active background cycle —
valkey.io/commands/expire.
Recap — set it, slide it, reclaim it
expire → bindA session deadline is absolute when it is fixed at sign-in and sliding when an EXPIRE after each
read pushes it a full window past now. Sliding keeps active users in and lets idle ones fall out. The engine makes
the deadline real two ways: lazily, deleting an expired key the instant a command reads it, and actively, sweeping
never-read expired keys in the background. EchoCache adds a jittered L1 clock and its own sweeper. The next dive
binds this deadline to the namespace it belongs to — keyed by a branded id, dropped on logout, refused at
the kind gate.
References
sources · relatedSources
- Valkey — EXPIRE — set a key’s time-to-live; the command rerun on each read to slide a session’s deadline, and the engine’s two reclamation paths.
- Valkey — SET — set a key with
PX; value and TTL in one command, the wayEchoCache.Table.putwrites L2. - Redis — TTL — read a key’s remaining life in seconds;
-2for a reclaimed key,-1for no deadline. - Sanfilippo, S. — antirez weblog — the Redis creator on expiry sampling and the cost of background eviction.
Related in this course
- R1.06 · Session managementthe module hub
- R1.06.1 · Hash, String & JSONthe previous dive
- R1.06.3 · The auth tie-inthe next dive
- R1 · Cachingthe chapter
- /echomqthe protocol the coherence lane rides