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:p is 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

  1. Forgetting TTL on derived keys: If you cache user:1001:profile with TTL 600 and also cache user:1001:friends with no TTL, the friends list lives forever.
  2. 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

  1. Using KEYS in production: KEYS * blocks the server. Use SCAN instead.
  2. Overly long keys: A 1KB key wastes memory across millions of entries.
  3. No namespace isolation: Without prefixes, key collisions are inevitable in shared instances.
  4. Hardcoded key strings: Use helper functions to build keys programmatically — reduces typos and enforces conventions.
  5. 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.