Everyday patterns: sessions, leaderboards and counters
Session storage with sliding expiry, sorted-set leaderboards with ties and time decay, atomic counters, HyperLogLog cardinality and set membership.
Session storage
HSET session:<id> user_id 42 role admin csrf <token> ip 203.0.113.9
EXPIRE session:<id> 1800
# sliding expiry: refresh on every request, but not on every request
# - refreshing on each call makes the key hot and the TTL meaningless
# - refresh only when more than 25 percent of the TTL has elapsed
# find a session by user for forced logout
SET user:42:session <session-id>
# log out everywhere
DEL session:<id> user:42:session- Store a session id in the cookie, never the session data. A cookie holding user data is either unsigned or a forge risk.
- Set a TTL and refresh it deliberately. A session with no expiry is a permanent credential if the cookie is stolen.
- Keep the session hash small: every request reads it, and a large hash adds latency to the hottest path.
EXPIREwith a random jitter avoids every session expiring at the same moment after a deploy.
💡
Sessions are durable state, so run them on an instance with
noeviction, or at least give every session key a TTL and allow volatile-lru. Losing a session is an annoying re-login; losing a queued job is data loss.Leaderboards with sorted sets
ZADD board:global 1500 user:42
ZINCRBY board:global 250 user:42
ZREVRANGE board:global 0 9 WITHSCORES
ZREVRANK board:global user:42
ZSCORE board:global user:42
ZCARD board:global
ZCOUNT board:global 1000 2000
# a paginated window around one player
ZREVRANGE board:global 90 110 WITHSCORES
# weekly board with automatic expiry
ZADD board:week:2026-38 1500 user:42
EXPIRE board:week:2026-38 1209600
# two-key union for an all-time-plus-recent board
ZUNIONSTORE board:combined 2 board:global board:week:2026-38 WEIGHTS 1 2| Requirement | Approach | Caution |
|---|---|---|
| Ties broken deterministically | Score and member both sort | Members sort lexicographically, so it is deterministic but arbitrary |
| Recency as a tie-break | Encode time in the low bits of the score | Use a fixed-width value or comparison breaks |
| Time decay | Recompute scores periodically, or use a decaying score | Decay needs a scheduled job or a write-time formula |
| Top N only | ZREVRANGE 0 N-1 | Trimming the tail keeps memory bounded |
| Very large boards | Shard by region and merge at read time | Cross-shard ranking is not a Redis feature |
# keep only the top 10k: sorted sets do not trim themselves
ZREMRANGEBYRANK board:global 0 -10001
# combine score and timestamp so ties break by who reached it first
# score = points, tie-break = (a large constant - unix seconds) / 1e6
# then ZREVRANGE gives the earlier achiever firstCounters and cardinality
INCR pageviews:home
INCRBY pageviews:home 10
GET pageviews:home
SET visits:tomorrow 1000 EX 86400 -- pre-seed with a TTL
# per-user totals with a bound
HINCRBY user:42:stats books_read 1
HINCRBY user:42:stats pages_read 340
# distinct visitors without storing them
PFADD visitors:2026-09-18 user:42 user:7 user:99
PFCOUNT visitors:2026-09-18
PFMERGE visitors:week visitors:2026-09-16 visitors:2026-09-17
PFCOUNT visitors:week -- 0.81 percent standard error, 12 KB per key
# membership and set operations
SADD online:2026-09-18 user:42
SISMEMBER online:2026-09-18 user:42
SINTERSTORE both:day1 online:2026-09-16 online:2026-09-17
SCARD both:day1- HyperLogLog answers "how many distinct" within about 0.81 percent and cannot tell you which ones. If you need the members, use a set and accept the memory.
INCRon a missing key treats it as zero, so a counter never needs initialisation - and an accidental typo silently creates a new counter.- Counters used for billing need exactness. Redis is not durable by default, so a counter that must survive a crash belongs in the transactional store or needs AOF with
appendfsync always. - Set operations on large sets are O(N) and block the server. Run them on a replica or use
SSCANand merge client-side.
FAQ
Can I use Redis as my only session store?
Yes, with persistence enabled and a plan for what happens when it is unavailable - a failover to a database-backed session store or accepting that users must log in again. Decide which before the incident, not during it.
How do I implement a leaderboard that respects recency?
Put the score in the integer part and a time component in a fractional or scaled part, so the sort is by points and then by time. Document the encoding, because a wrong scale factor produces a leaderboard that silently orders wrong.
Related
Streams and reliable queue processing Key design, memory and eviction
Last refreshed 2026-09-18.