iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Keys & TTL

Redis keys are flat strings, but the conventions you choose determine ergonomics, TTLs, sharding, and scanning.

Redis — key design

EXAMPLE
# ===== Keys are bytes (typically UTF-8 strings) =====
# Length limit: 512MB (practically: keep them short).
# The data type lives at the key. SET user:1 'alex' creates a String type at 'user:1'.

# ===== Naming convention: <domain>:<entity>:<id>[:<attr>] =====
# Good:
#   user:1                # full user hash
#   user:1:friends        # set of friend ids for user 1
#   session:abc123        # session token
#   rl:login:198.51.100.7 # rate-limit counter
#
# Bad:
#   u_1, user/1, user|1     -- inconsistent
#   user_first_name_alex    -- value-in-key; impossible to mutate cleanly
#   user:1:profile:settings:theme  -- too deep; pick the right type instead

# Use colons as separators (Redis tooling recognises them).

# ===== TTLs are first-class =====
SET session:abc123 "user_42" EX 1800       # expire in 30 minutes
EXPIRE session:abc123 3600                 # extend TTL
PERSIST session:abc123                     # remove TTL
TTL session:abc123                         # seconds remaining (-1 = no TTL, -2 = missing)

# Pattern: always set a TTL on caches and sessions. Long-lived keys belong in a real DB.

# ===== Multi-set / multi-get =====
MSET a 1 b 2 c 3
MGET a b c
DEL b c

# ===== Existence and counts =====
EXISTS user:1 user:2          # count of keys that exist
DBSIZE                        # number of keys in current DB

# ===== Scanning safely (don't use KEYS in prod) =====
# KEYS is O(N), blocks the server.
# SCAN is cursor-based, non-blocking.
SCAN 0 MATCH 'session:*' COUNT 200
# Repeat until cursor returns 0.

# In code (node-redis):
# const stream = client.scanIterator({ MATCH: 'session:*', COUNT: 200 });
# for await (const key of stream) { /* ... */ }

# Type-specific scans:
HSCAN user:1 0 MATCH 'pref:*' COUNT 50
SSCAN user:1:friends 0 COUNT 50
ZSCAN leaderboard 0 COUNT 50

# ===== Atomic counters =====
INCR rl:login:198.51.100.7
EXPIRE rl:login:198.51.100.7 60        # set TTL on first hit
# Idempotent pattern: INCR + EXPIRE NX in a pipeline / transaction.

# ===== Hash tags for cluster routing =====
# In Redis Cluster, keys hash to slots. Hash tag forces keys to the same slot.
# Bracket-wrapped substring = the only part used for hashing.

SET {user:1}:profile "..."
SET {user:1}:settings "..."
# Both live on the same node. Necessary for multi-key ops (MGET, transactions, scripts).

# ===== Renaming + copying =====
RENAME session:old session:new
COPY user:1 user:1:backup TTL EX 86400

# ===== Patterns to internalise =====
# - <domain>:<entity>:<id>[:<attr>] naming, colons as separators
# - TTL on everything cache-shaped; KEEPTTL on rewrites you don't want to extend
# - SCAN, never KEYS, in any environment with real traffic
# - Hash tags when multi-key ops are required in a cluster
# - One Redis DB per environment (number); avoid DB indexes for multi-tenant separation

# ===== Pitfalls =====
# - KEYS * in prod -> server stalls; alarms fire
# - Putting mutable values inside the key name -> cannot update without delete+set
# - Forgetting TTL on a 'temporary' cache key -> grows forever
# - 4MB+ keys (e.g. caching pages) -> memory pressure, slow eviction
# - Sharing one Redis between unrelated apps without prefixes -> noisy collisions

Why it matters

Key naming is the API of your Redis. Pick a colon-separated convention, set TTLs by default, scan instead of keys, and use hash tags when the cluster forces multi-key locality. Once these are reflex, you can read another teams Redis without asking what anything means.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
SET name "Ada"
GET name
EXISTS name           # 1
TTL name              # -1 (no expiry)
EXPIRE name 60        # expires in 60s
DEL name
Try it Yourself »

Exercise

Set a TTL of 60 seconds on a key.

session 60

Test yourself

Q1. Set an expiry in seconds with…
Q2. Check if a key has TTL with…
Q3. DEL on a missing key returns…

Discussion

Loading…