Chapter 02 · Keys, Namespaces, Expiration, TTL, Scanning, and Data-Type Introspection
Key Naming Schemes, Namespaces, Hash Tags, Cardinality, and Operational Discoverability
Design Redis key names that remain discoverable and collision-resistant, understand namespaces and Cluster hash tags, bound cardinality, and verify keyspace state safely.
Learning outcomes
AtlasMart has reached the point where several teams write to the
same Redis server. The cart service writes cart:42,
a test harness writes the same short name, a marketing job
creates one key per page view, and an engineer proposes
“database 3 for tenant C” as isolation. None of those choices is
a Redis syntax error, but together they create collisions,
unbounded cardinality, weak discoverability, and a false
security model. Before AtlasMart adds richer data structures, it
needs a key-addressing discipline that humans and tools can
reason about.
Design stable key names whose parts communicate ownership, environment, entity, identifier, and purpose without pretending that naming itself enforces access control.
Distinguish an application namespace, a standalone Redis logical database, and a Redis Cluster hash slot; explain what a Cluster hash tag changes.
Measure key cardinality and inspect representative keys with EXISTS, TYPE, TTL/PTTL, SCAN, DBSIZE, and MEMORY USAGE.
Design discovery workflows that do not depend on a full blocking KEYS operation or on exact SCAN enumeration semantics.
Repair a namespace-collision and tenant-isolation mistake, then leave a bounded, auditable AtlasMart fixture for the rest of Chapter 02.
All Chapter 02 labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from
Docker Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled because
traffic stays on loopback, default user disabled, named ACL
users atlasmart-app and
academy-admin, logical database 0, AOF with
appendfsync everysec plus RDB snapshots, and a
persistent /data Docker volume. No explicit
maxmemory limit or eviction policy is introduced
in this chapter. Search, JSON, vector, time-series, and
probabilistic features are not required for these exercises.
The braces used by a Redis Cluster hash tag are taught now for
naming discipline, but the lab remains standalone; braces
therefore have no routing effect in this chapter.
1. A Redis key is an address, so naming is part of the data model
A key is the binary-safe name by which Redis
locates a value. The set of keys visible in one selected logical
database is its keyspace. Redis does not
interpret colons in a key name;
atlasmart:prod:cart:431 is just one sequence of
bytes. Humans and applications give those separators meaning. A
namespace is therefore an application
convention: a predictable prefix or naming grammar used to avoid
collisions and make ownership discoverable.
For AtlasMart, a useful grammar is
atlasmart:<environment>:<domain>:<identifier>:<purpose>. The grammar should encode stable facts that operators need
when investigating a key. It should not embed secrets,
personally identifying values that do not need to be exposed in
key names, or volatile implementation details. Long names
consume memory on every key; very short ambiguous names save
bytes at the cost of operational confusion. The right choice is
evidence-based and workload-specific, not “always use exactly N
characters.”
| Candidate | Interpretation | Judgment |
|---|---|---|
cart:431 |
Short cart identifier | Collision-prone when several applications share a server; ownership/environment are invisible. |
atlasmart:prod:cart:431 |
Product + environment + domain + id | Good baseline for a standalone key when those components are stable. |
atlasmart:prod:cart:{{431}}:items |
Adds a Cluster hash tag around 431 | Useful only when future multi-key operations really need keys sharing this tag to share a hash slot. |
atlasmart:prod:request:<uuid> for every
request forever
|
Per-request unique key | Operationally dangerous unless lifetime/cardinality is explicitly bounded. |
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app SET atlasmart:prod:cart:431 "open"docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app EXISTS atlasmart:prod:cart:431docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app TYPE atlasmart:prod:cart:431# Expected shape:# OK# (integer) 1# string
EXISTS proves that a key is currently addressable;
TYPE reports the Redis data type stored at that
key. Neither command proves who owns the key, whether its value
is fresh, or whether the prefix is secure. Those are separate
contracts.
2. Namespace conventions are not security boundaries
Redis Access Control Lists (ACLs) authenticate
users and authorize commands and key/channel patterns. A naming
prefix can make an ACL rule practical—for example, the Chapter
01 application user is restricted to
~atlasmart:*—but the prefix itself does not deny
anything. If two tenants both authenticate as the same user with
permission for ~atlasmart:*, writing
atlasmart:tenant-a:... and
atlasmart:tenant-b:... does not create tenant
isolation. Application authorization, separate ACL users/key
patterns, network boundaries, or separate Redis deployments may
be required depending on the threat model.
A logical database is a numbered keyspace
selected with SELECT in standalone Redis. It is
convenient for some development separation, but it is not an ACL
boundary: authentication and server resources are shared. Redis
Cluster deliberately does not provide multiple logical databases
in the same way; Cluster distributes keys through hash slots and
uses database 0. Therefore “put each tenant in DB 1, 2, 3” is
neither portable to Cluster nor a complete security
architecture.
Ask what mechanism would reject an unauthorized command. A colon prefix cannot reject it. An ACL rule, application authorization check, network policy, or separate deployment can. Naming supports those controls; it does not replace them.
3. Hash tags affect Cluster placement, not standalone meaning
Redis Cluster is Redis’s built-in sharding topology: keys are mapped into 16,384 hash slots, and those slots are assigned to cluster nodes. A hash tag is the non-empty substring between the first valid pair of braces in a key. When present, Cluster hashes that substring rather than the whole key, allowing deliberately related keys to map to the same slot. This matters because many multi-key commands and server-side operations in Cluster require all participating keys to be in one slot.
Hash tags should encode the smallest stable grouping that truly
needs slot locality. Tagging every cart key with
{cart} would force all carts into one slot and
create skew; tagging related keys with {431} can
preserve locality for cart 431 without collapsing every cart
together. In the Chapter 02 standalone lab,
{431} is just part of the key name. We teach it now
so names do not need a disruptive rewrite later.
| Key | Cluster tag used | Design consequence |
|---|---|---|
atlasmart:cart:{{431}}:items |
431 |
Related cart-431 keys can share a slot. |
atlasmart:cart:{{431}}:totals |
431 |
Same slot as the items key. |
atlasmart:cart:{{}}:items |
No non-empty tag | The normal whole-key hash is used. |
atlasmart:{{cart}}:431 |
cart |
All similarly tagged carts target one slot; likely poor distribution. |
Do not use braces merely because they look tidy. Slot locality
is a topology decision with load-distribution consequences.
Chapter 21 will create a real Cluster and verify slot movement,
MOVED/ASK redirections, and hash-tag
behavior.
4. Cardinality is a capacity property, not just a count
Cardinality here means the number of distinct keys. Every key has metadata overhead in addition to its value, and more keys increase the work needed for discovery, expiry bookkeeping, persistence, replication, backup, and Cluster operations. A million tiny values are not equivalent to one large value merely because their payload bytes sum to the same number.
DBSIZE returns the current number of keys in the
selected logical database. It is useful as an inventory signal,
not a breakdown by namespace. SCAN can
incrementally visit keys and MATCH can filter
names, but its cursor semantics are intentionally not snapshot
semantics. A production cardinality policy should therefore
combine application-level metrics (keys created, retained,
expired, deleted), Redis database counts, memory metrics, and
representative sampling rather than periodically blocking the
server to recount everything.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin DBSIZEdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin MEMORY USAGE atlasmart:prod:cart:431docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin SCAN 0 MATCH 'atlasmart:prod:cart:*' COUNT 20# DBSIZE is an exact current count for DB 0 at command execution.# MEMORY USAGE is an estimate of bytes attributable to this key/value according to this Redis build.# SCAN returns: 1) a new cursor, 2) zero or more matching keys.
The exact byte count from MEMORY USAGE depends on
Redis version, allocator behavior, internal encoding, and
sampling for nested types. It is evidence for this server, not a
universal per-key constant. Redis 8.10 also changed some
accounting for compact hash representations, another reason to
record the server version beside measurements.
5. Operational discoverability means safe questions have cheap answers
A discoverable key design lets an operator answer “does it
exist?”, “what type is it?”, “does it expire?”, “roughly how
large is it?”, and “what family does it belong to?” without
reading the whole dataset. TTL reports remaining
lifetime in seconds, PTTL in milliseconds; both
return negative sentinel values for persistent or missing keys.
SCAN is an incremental iterator; a cursor of 0
starts a full iteration and a returned cursor of 0 ends it.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app SET atlasmart:prod:session:1001 "customer=431" EX 120docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app SET atlasmart:prod:session:1002 "customer=772" EX 300docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app PTTL atlasmart:prod:session:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin SCAN 0 MATCH 'atlasmart:prod:session:*' COUNT 10# PTTL should be positive and decrease over time.# The SCAN cursor may already return 0 for this tiny fixture; that does not generalize to a large keyspace.
Because these session keys were created with
EX 120 and EX 300, they are
volatile keys: keys with expiration metadata.
If PTTL reports -1, the key exists but
is persistent; if it reports -2, the key is
missing. Lesson 2 proves these lifecycle states and mutation
rules in detail.
6. Deliberately wrong design: prefixes and DB numbers as isolation
Suppose two AtlasMart components both choose
cart:431, while the platform team tells one tenant
to use logical database 1 and assumes that this is “secure
enough.” The collision is concrete in DB 0: whichever writer
runs last replaces the string value. The isolation claim is also
false: any authenticated identity allowed to use
SELECT and access those keys can cross the
database-number convention, and a future Redis Cluster migration
removes the multi-database assumption.
# WRONG: two owners choose the same ambiguous key.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app SET atlasmart:ch02:collision "cart-service"docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app SET atlasmart:ch02:collision "test-harness"docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app GET atlasmart:ch02:collision# "test-harness" -- the first owner's state was overwritten.# REPAIR: encode stable ownership/purpose in distinct keys.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user atlasmart-app MSET \ atlasmart:prod:cart:431:state "open" \ atlasmart:test:fixture:cart:431:state "synthetic"docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin SCAN 0 MATCH 'atlasmart:*:cart:431:*' COUNT 20
The repaired names make the collision less likely and discovery easier. They still do not create authorization. If production requires tenant isolation, pair stable prefixes with independently reviewed ACL/application/network/deployment controls and test an unauthorized access attempt.
7. Hands-on lab: build the Chapter 02 key inventory
This lab creates a bounded synthetic dataset under
atlasmart:ch02:. It does not call
FLUSHDB or FLUSHALL. Cleanup targets
only known chapter prefixes. The objective is to produce an
evidence card, not a benchmark.
# Create four key families with bounded cardinality.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 sh -lc 'for i in $(seq 1 12); do redis-cli --user atlasmart-app SET "atlasmart:ch02:product:$i:summary" "p-$i" >/dev/null; done; for i in $(seq 1 6); do redis-cli --user atlasmart-app SET "atlasmart:ch02:session:$i" "s-$i" EX 600 >/dev/null; done'# Collect evidence.docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin DBSIZEdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin EXISTS atlasmart:ch02:product:1:summarydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin TYPE atlasmart:ch02:product:1:summarydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin TTL atlasmart:ch02:session:1docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin MEMORY USAGE atlasmart:ch02:product:1:summarydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 \ redis-cli --user academy-admin SCAN 0 MATCH 'atlasmart:ch02:*' COUNT 100
Expected evidence is shape-based rather than byte-for-byte:
EXISTS returns 1, TYPE returns
string, the session TTL is positive and at most 600
when observed, memory usage is a positive integer, and a full
SCAN iteration eventually returns the fixture keys. If the first
SCAN returns a nonzero cursor, continue with exactly that cursor
until Redis returns 0.
Verification checklist:
- You can explain every component of the AtlasMart naming grammar and which components are conventions rather than Redis-enforced hierarchy.
- You can state why logical database numbers are not tenant/security boundaries and why Redis Cluster changes the database model.
- You can explain how a hash tag influences a future Cluster slot without claiming that braces do anything in this standalone lab.
- You recorded current DBSIZE, TYPE, TTL/PTTL, MEMORY USAGE, and SCAN evidence without using KEYS for inventory.
- You can identify at least one cardinality budget for each new key family: creation rate × retention window plus exceptional backlog.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 sh -lc 'for i in $(seq 1 12); do redis-cli --user atlasmart-app UNLINK "atlasmart:ch02:product:$i:summary" >/dev/null; done; for i in $(seq 1 6); do redis-cli --user atlasmart-app UNLINK "atlasmart:ch02:session:$i" >/dev/null; done; redis-cli --user atlasmart-app UNLINK atlasmart:ch02:collision >/dev/null; redis-cli --user atlasmart-app UNLINK atlasmart:prod:cart:431 atlasmart:prod:cart:431:state atlasmart:test:fixture:cart:431:state atlasmart:prod:session:1001 atlasmart:prod:session:1002 >/dev/null'
8. Production judgment
Key naming is appropriate as an operational and modeling convention, not as an authorization mechanism. Bound cardinality before launch: estimate creation rate, retention, fan-out per entity, and failure backlog; then measure actual key counts, memory, persistence growth, and tail latency. Remember that persistence writes key changes to disk according to the configured RDB/AOF policy, replication later copies mutations asynchronously to replicas, Sentinel later automates failover, and Cluster later partitions keys by slots. None of those mechanisms changes the basic need for stable names.
Search indexes, vector retrieval, time-series structures, and probabilistic structures can add secondary memory and write costs later, but they do not make the primary keyspace disappear. Client retries can also create duplicate or abandoned keys if key generation is not idempotent. Test namespace collisions, expired/missing keys, bounded bulk creation, scan under mutation, and cleanup during failure. Keep migration and rollback in mind: renaming millions of keys or changing hash tags can be operationally expensive and, in Cluster, can alter slot placement.
9. Summary and next step
A Redis key name is a binary-safe address. Colons and prefixes are application conventions; ACLs and other controls enforce security. Logical databases are separate keyspaces in standalone Redis but not a tenant-isolation mechanism, while Redis Cluster uses 16,384 hash slots and optional hash tags for placement. Cardinality is a capacity concern, and safe discoverability starts with bounded names plus incremental inspection. Next, you will make each key’s lifetime equally explicit by proving expiration semantics command by command.
Check your understanding
-
Why is
atlasmart:tenant-a:a useful prefix but not a security boundary? - What does a Cluster hash tag change?
- Why is DBSIZE insufficient for namespace capacity planning?
- What does PTTL = -1 versus -2 mean?
- Why should a future Cluster migration influence naming today?
Review the answers
1. It improves naming and can be referenced by ACL rules, but the bytes in the name do not reject unauthorized commands. Enforcement comes from ACLs, application authorization, network/deployment controls, or other security mechanisms.
2. In Redis Cluster, the first valid non-empty brace substring is hashed for slot selection so intentionally related keys can share a slot. In a standalone server it has no routing effect.
3. It is an exact current count for the selected database, not a per-prefix time series and not a forecast. You also need creation rate, retention, memory, skew, expiration/deletion, and failure backlog.
4. -1 means the key exists with no expiration; -2 means the key does not exist at observation time.
5. Hash tags and the lack of multiple logical databases in Cluster can make key-name and DB-number assumptions costly to change later, so topology-aware naming reduces migration risk.
Authoritative references
- Redis keyspace documentation — keys, logical databases, expiration, and keyspace operations
- SCAN command — cursor iteration and documented guarantees
- DBSIZE command — current selected-database cardinality
- MEMORY USAGE command — per-key memory accounting and sampling
- Redis Cluster specification — 16,384 slots, hash tags, and cluster keyspace semantics
- Redis ACL documentation — user, command, key, and channel authorization