Topic 0.5
Key Design: Names, Namespaces, Tenants, and Versions
In one line
Keys are Redis's only index, so their names are your schema. Good keys are predictable, namespaced, tenant-aware and versioned, and they're designed with cluster slots, memory and migrations in mind.
Think of it like this
A warehouse with a strict shelf-labelling system, like aisle 12, bay 4, shelf C. Anyone can find anything, two products never share a label, and when the layout changes you relabel in a planned way. A warehouse where people write "stuff" on boxes works until it doesn't.
Key ideas
- 01
Use a consistent, colon-separated hierarchy:
<service>:<entity>:<id>[:<field>], for examplecart:user:123,product:456:views,session:9f3c...,rl:api:v1:user:123:1727520000. The colon is only a convention (Redis treats the key as bytes), but tools, dashboards andSCANpatterns rely on it. - 02
Prefix by owner to prevent collisions between services and to scope permissions: an ACL can allow
~checkout:*for one service only. In multi-tenant systems, put the tenant early (t:acme:user:123) so you can scan, count and delete one tenant's data, and so a noisy tenant is visible in key analysis. - 03
Version keys that hold serialised objects:
product:v3:456. When a deployment changes the format, new code reads and writesv3while old pods still usev2; old keys expire naturally. This avoids crashes from deserialising an old format and makes rollbacks safe. The same trick invalidates a whole cache namespace at once (bumpv3tov4). - 04
Plan for Redis Cluster from day one: multi-key commands (
MGET, transactions, Lua over several keys) only work when all keys hash to the same slot. Hash tags ({user:123}:cart,{user:123}:profile) force that, since only the part inside{}is hashed (Topic 11.2). Choose tags that group data you use together, without making one tag enormous. - 05
Keep keys short but readable: a key is stored once per entry, so 100M keys × 40 extra bytes = 4 GB. Don't encode large values in keys, and don't put unbounded user input in key names without validating it (it's a memory and collision risk).
- 06
Put a TTL on every key that doesn't need to live forever, and decide it at design time. Most memory leaks in Redis are "keys without TTL that nobody deletes".
Code & diagrams
# service : entity : id : facet
SET catalog:product:v3:456 "{...json...}" EX 3600
HSET session:9f3c2b profile:user 123 created 1727520000
EXPIRE session:9f3c2b 1800
INCR rl:api:user:123:1727520000 # fixed-window rate limit bucket
SADD t:acme:user:123:roles admin # tenant-scoped
# Cluster-ready: same hash tag, same slot
HSET {user:123}:profile name "Asha"
HSET {user:123}:cart sku:9 2
CLUSTER KEYSLOT {user:123}:profile # (integer) 5236
CLUSTER KEYSLOT {user:123}:cart # (integer) 5236Centralise key construction; never build keys with ad-hoc string concatenation across the codebase.
public final class Keys {
private static final String PRODUCT_VERSION = "v3";
public static String product(long id) {
return "catalog:product:" + PRODUCT_VERSION + ":" + id;
}
public static String cart(long userId) {
return "{user:" + userId + "}:cart"; // colocated with profile
}
public static String rateLimit(long userId, long windowStartSec) {
return "rl:api:user:" + userId + ":" + windowStartSec;
}
}A safe background migration: iterate with SCAN, copy with the new name, let old keys expire.
redis-cli --scan --pattern 'product:*' --count 1000 | while read -r key; do
id="${key#product:}"
# COPY keeps the value and TTL (Redis 6.2+); NX-like behaviour: fails if target exists
redis-cli COPY "$key" "catalog:product:v3:$id" > /dev/null
done
# Readers try the new key first and fall back to the old one during the migration window.Interview problem
The problem
Key design for a multi-tenant SaaS
Design the Redis key scheme for a multi-tenant SaaS with sessions, per-tenant feature flags, per-user rate limits, and cached dashboards. It will run on Redis Cluster. Support deleting all data for a tenant, and changing the dashboard cache format without downtime.
You're given
- 5,000 tenants, some very large
- Redis Cluster, 6 shards
- GDPR tenant deletion
- Weekly deployments
The interviewer follows up
Why not use Redis logical databases (SELECT 0–15) to separate tenants?
Your SCAN for tenant deletion finds nothing on some keys. Why?
When it breaks
A deployment changes the serialised format without changing the key
What you see
During the rolling deploy, old pods read new-format values (or the reverse) and throw deserialisation errors; some requests fail until all pods are updated or the cache is flushed.
Fix & prevent
Version the key namespace (v3 → v4) whenever the format changes; keep formats backward-compatible when you can (Topic 8.3).
Keys built from raw user input
What you see
An attacker sends millions of unique IDs, creating millions of keys (memory growth) or colliding with other namespaces via crafted separators.
Fix & prevent
Validate and normalise IDs before building keys, cap key counts per user, always set TTLs, and hash long or untrusted parts.
Explain it without notes
Why is key versioning useful during rolling deployments?
How does a hash tag change which cluster slot a key belongs to, and what's the risk?
Practice
Write key names for: a user's unread notification count, a product's view counter for one day, and a cached search result for a query string.
Run CLUSTER KEYSLOT (it works on a standalone server) for {order:9}:items and {order:9}:status. Why do they match?
Trade-offs
- ↔
Readable long keys vs memory: at hundreds of millions of keys, shorter prefixes save gigabytes; at smaller scales readability wins.
- ↔
Hash tags enable multi-key atomicity in Cluster but concentrate load; tag by the smallest unit you need atomicity for (user, order), never by tenant or "global".
Done when you can
I can design a namespaced, tenant-aware, versioned key scheme.
I can migrate a key format without downtime.
I can explain hash tags and choose tags that won't create hot shards.
Every key I design has a TTL decision attached.