builds / redis / stage-5SHEET 5 / 6 · REV ASIGN IN
ASSEMBLY DIAGRAM — YOUR REDISSCALE: LEARNING
clientthe outside worldPORT JACK✓ builtRESP CODEC✓ builtMEMORY BANK✓ builtINTERLOCK✓ builtCLOCK UNIT⚙ buildingFLIGHT RECORDERstage 6
BUILTUNDER CONSTRUCTIONNOT YET IMAGINED INTO EXISTENCE
STAGE 5 · THE CLOCK UNIT

The Hourglass

Caches forget on purpose.

What you're wiring up

This stage attaches deadlines to keys: SET k v PX 100 means "this key dies in 100 ms." The elegant idea: expiry needs no timer-per-key. Lazy expiration — every read first checks "is this key past its deadline?" and deletes it on the spot — is correct all by itself. A background active sweep merely keeps memory from silting up with keys nobody reads.

You'll also meet time-as-data: store the absolute deadline (time.Now().Add(d)), never the remaining duration, or every check drifts.

Assembly steps

[ 01 ]
Extend the stored entry to {val string; expiresAt time.Time} (zero time = immortal). Implement SET … EX <sec> and PX <ms>.
hint

Parse SET's options after arg 2 in a loop — you're building a tiny option grammar.

[ 02 ]
Lazy expiry: in Get/Exists/Incr, if the deadline is set and past, delete and behave as if it never existed.
hint

Plain SET (no EX/PX) on an existing key wipes the old TTL — Redis semantics; the harness checks this exact case.

[ 03 ]
Implement TTL (seconds, rounded up) / PTTL (ms): -2 if no key, -1 if no expiry. Plus EXPIRE key <sec> and PERSIST key.
[ 04 ]
Active sweep: a background goroutine ticking every 100 ms, deleting expired entries under the same interlock.
hint

time.NewTicker + for range ticker.C, started at server boot. Take the write lock per sweep; scanning all keys is fine at this scale.

Go deeper (after it passes)

Pairs with HFDP M1 — Strategy (lazy vs active expiry as swappable policies) and DDIA M8 — Everything Fails (time is data).