Redis Key Design Best Practices: Prefixes, Versioning & TTL
Poorly designed Redis keys are a ticking time bomb. They lead to namespace collisions, make debugging painful, and can silently break your application during migrations. This guide covers battle-tested patterns for key design that scale.
The Problem
Imagine you have a growing application. Multiple services share the same Redis instance. One team uses user:123 for session data, another uses user:123 for profile cache. Boom — silent data corruption with zero errors in your logs.
Key Naming Conventions
A good Redis key should be self-documenting. The widely adopted convention uses colons as separators:
<service>:<entity>:<id>:<field>
Example in Editor
SET app:user:1001:profile '{"name":"Alice","role":"admin"}'
SET app:user:1001:session "abc123token"
SET app:order:5001:status "shipped"
GET app:user:1001:profile
Rules of Thumb
- Use colons
:as delimiters (Redis convention, also used by Redis Cluster for hash slots). - Keep keys under 512 bytes. Shorter keys save memory —
u:1001:pis tempting but unreadable. Find the balance. - Avoid spaces and special characters.
- Use lowercase consistently.
Prefix Strategy for Multi-Tenant / Multi-Service
When multiple services share a Redis instance, prefix with the service name:
SET auth:session:user:1001 "token_xyz" EX 3600
SET cache:product:2001 '{"name":"Widget","price":9.99}' EX 600
SET ratelimit:api:user:1001 "15" EX 60
This makes it trivial to:
- Monitor per-service memory usage with
SCAN+ pattern matching. - Flush a single service's data without nuking everything.
- Set different eviction policies per logical namespace (via separate databases or instances).
Key Versioning
When your data schema changes, old cached data can cause bugs. Key versioning solves this:
SET cache:v2:user:1001 '{"name":"Alice","role":"admin","dept":"eng"}'
GET cache:v2:user:1001
During migration, you can read from v2 and fall back to v1:
# Application pseudocode:
# data = GET cache:v2:user:1001
# if data is nil:
# data = migrate(GET cache:v1:user:1001)
# SET cache:v2:user:1001 data
After migration completes, clean up old keys:
SCAN 0 MATCH cache:v1:* COUNT 100
TTL Management
Every cache key should have a TTL. Keys without TTL are memory leaks waiting to happen.
Setting TTL
SET session:user:1001 "token_abc" EX 3600
TTL session:user:1001
SET temp:otp:user:1001 "482910" PX 120000
PTTL temp:otp:user:1001
Refreshing TTL on Access
For session-like data, refresh TTL on each access to implement sliding expiration:
GET session:user:1001
EXPIRE session:user:1001 3600
Or use GETEX (Redis 6.2+) for atomic get-and-refresh:
SET session:user:1001 "token_abc" EX 3600
GETEX session:user:1001 EX 3600
Common TTL Pitfalls
- Forgetting TTL on derived keys: If you cache
user:1001:profilewith TTL 600 and also cacheuser:1001:friendswith no TTL, the friends list lives forever. - TTL drift: If you SET a key without EX, it removes the existing TTL. Always include EX/PX when updating cached values.
SET mykey "value1" EX 300
TTL mykey
SET mykey "value2"
TTL mykey
Run the above — you'll see the TTL disappears after the second SET. This is a classic production bug.
Key Expiration Patterns
Lazy + Active Expiration
Redis uses two mechanisms:
- Lazy expiration: Key is checked on access. If expired, it's deleted and nil is returned.
- Active expiration: Redis periodically samples random keys with TTL and deletes expired ones.
This means expired keys may linger in memory briefly. For memory-sensitive workloads, keep this in mind.
Keyspace Notifications
You can subscribe to expiration events for cleanup logic:
CONFIG SET notify-keyspace-events Ex
Then subscribe to __keyevent@0__:expired in your application to trigger side effects when keys expire.
Pitfalls to Avoid
- Using KEYS in production:
KEYS *blocks the server. UseSCANinstead. - Overly long keys: A 1KB key wastes memory across millions of entries.
- No namespace isolation: Without prefixes, key collisions are inevitable in shared instances.
- Hardcoded key strings: Use helper functions to build keys programmatically — reduces typos and enforces conventions.
- Ignoring key cardinality: If your key pattern generates millions of unique keys, plan your memory budget accordingly.
Try It in the Editor
Head over to the Redis Online Editor and experiment with these patterns:
SET app:v1:user:1001:profile '{"name":"Alice"}' EX 600
TTL app:v1:user:1001:profile
SET app:v1:user:1001:profile '{"name":"Alice","v":2}'
TTL app:v1:user:1001:profile
Notice how the TTL disappears after the second SET? That's the kind of subtle bug that bites you in production. Practice here, not on your live servers.