jonnify·redis /redis-patterns/caching/session-management/ttl-expiry

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.

Absolute vs sliding. Three requests at 0, 10, 20 minutes; the next is at 50. Select a mode to read where its deadline falls.
t=0 10m 20m 30m 50m sliding — deadline rides to the last request + 30m = 50m absolute — deadline fixed at 30m from sign-in; the 50m request finds nothing
readout · Select a mode. A sliding deadline rides each request forward; an absolute deadline is fixed at sign-in.
§1

Fixed at sign-in, or moved on access

source · Absolute vs sliding

A 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 EXPIRE at sign-in. The session ends at a fixed instant, busy or not. A hard cap.
sliding
An EXPIRE after each read, resetting the deadline to a full window past now. Idle-based expiry.
TTL key
Read the remaining time-to-live in seconds; -2 means the key is gone, -1 means 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.

§2

How a key is actually reclaimed

source · Session Data Cleanup

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

Reclaim a key — lazy on read, active in the background. Choose a key state and the time since the last read; the readout names the mechanism and the next read’s answer.
600

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.

§3

The deadline on EchoCache

table.ex:290, :484, :350 · verbatim

EchoCache 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 application · the jittered clock and the sweeper

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:

content/bcs4.1.md · the jittered clock and the sweeperfrozen
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
the pattern

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.

its EchoCache application

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.

§4

Recap — set it, slide it, reclaim it

expire → bind

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

§5

References

sources · related

Sources

  • 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 way EchoCache.Table.put writes L2.
  • Redis — TTL — read a key’s remaining life in seconds; -2 for a reclaimed key, -1 for no deadline.
  • Sanfilippo, S. — antirez weblog — the Redis creator on expiry sampling and the cost of background eviction.

Related in this course