0% completed
1.5 Keys and TTLs
On This Page
Key Naming Conventions
The Role of Key Names
A Practical Convention
TTLs and Expiration
Setting TTLs
How Writes Affect a TTL
Uses of TTLs
Lazy Expiration
Active Expiration
Consequences of Lazy and Active Expiration
Common Failure Modes
Using This in an Interview
FAQs
Key Takeaways
Four commands on one session key show how a time to live can be lost without any error:
127.0.0.1:6379> SET shop:session:abc123 "user=42" EX 1800 OK 127.0.0.1:6379> TTL shop:session:abc123 (integer) 1800 127.0.0.1:6379> SET shop:session:abc123 "user=42;cart=3" OK 127.0.0.1:6379> TTL shop:session:abc123 (integer) -1
The first SET gives the key a lifetime of 1,800 seconds. The second SET replaces the value without an expiry option, so Redis also removes the expiry, and TTL returns -1, which means "this key never expires". A key that was meant to live for 30 minutes now lives until someone deletes it.
Mistakes like this matter more in Redis than in a disk-based database. A disk-based database slows down gradually as data grows, but Redis keeps everything in RAM, so memory is both its performance and its hard limit. When memory runs out, Redis either refuses writes, evicts data you wanted, or the operating system kills the process. Key names, TTLs and memory settings are therefore the core of operating Redis well, and this lesson follows the shop's session and cache keys through the first two. The next lesson covers memory settings.
Key Naming Conventions
The Role of Key Names
Redis has one flat keyspace with no tables or schemas, so the key name is your only namespace. Poor naming causes collisions between applications, makes debugging slow, and makes it impossible to see which part of the application uses the memory.
A Practical Convention
Use colon-separated segments from general to specific: the service or domain, the entity type, the identifier, and then an optional attribute. Examples are shop:user:1234:profile, shop:session:abc123, shop:cart:1234 and orders:lock:9981. These names read naturally, group related keys when you scan or use tools, and let monitoring and cleanup work by prefix.
These guidelines hold in practice:
- Be consistent. Pick one separator, one casing and one order of segments, and document them.
- Include a namespace or version. A prefix such as the application name prevents collisions on shared instances. A version segment such as
v2lets you change a serialization format without clashing with old data. - Keep keys short but readable. Every key name is stored in memory. Across 100 million keys, an extra 30 bytes per key costs 3 GB, but on small keyspaces clarity is worth more than a few bytes.
- Never put unbounded or sensitive data in keys. Do not use full URLs, raw email addresses or large text in key names. Use IDs or hashes of the value instead.
- Design for the cluster. Redis Cluster assigns every key to one of its 16,384 hash slots, and a multi-key operation needs all its keys in the same slot. A hash tag, the part of the key inside braces, makes Redis hash only that part, so
{user:1234}:profileand{user:1234}:cartland in the same slot. Use hash tags deliberately, because overusing them puts too much load on one slot. - Avoid giant keys. A single key that holds millions of elements is slow to read, expire and delete, and it blocks the single execution thread. Split large structures into smaller buckets.
- Never use KEYS in production. KEYS walks the entire keyspace on the main thread. SCAN returns the keys in small batches with a cursor, so other clients are served between batches.
TTLs and Expiration
Setting TTLs
A TTL (time to live) is a lifetime after which a key deletes itself. EXPIRE sets it in seconds and PEXPIRE in milliseconds, EXPIREAT and PEXPIREAT set an absolute Unix timestamp, and SET accepts the same choice through its EX, PX, EXAT and PXAT options. TTL and PTTL report the remaining time, EXPIRETIME (Redis 7.0) reports the absolute expiry timestamp, and PERSIST removes the expiry. GETEX reads a value and sets or removes its expiry in the same command.
Since Redis 7.0, EXPIRE accepts conditions: NX sets an expiry only when the key has none, XX only when it already has one, and GT or LT only when the new expiry is later or earlier than the current one. For example, EXPIRE shop:session:abc123 1800 GT extends a session but never shortens it.
How Writes Affect a TTL
Several behaviors follow from how Redis stores the expiry. SET, as the opening example showed, removes an existing TTL unless you add the KEEPTTL option. Commands that change a value in place, such as INCR, APPEND, LPUSH or HSET, keep the TTL. RENAME moves the expiry along with the key. EXPIRE with a zero or negative timeout deletes the key immediately.
The figure follows shop:session:abc123 through four commands in order. SET ... EX 1800 starts the TTL at 1,800 seconds. Ten minutes later, APPEND shop:session:abc123 ";cart=3" changes the value in place, and TTL reports 1,200. SET ... KEEPTTL then replaces the value and TTL still reports 1,200. A final plain SET replaces the value again, and TTL reports -1. Notice that only that last command changes the expiry, while the two commands before it leave the remaining time unchanged.
Since Redis 7.4, a single field of a hash can also have its own TTL, set with HEXPIRE and read with HTTL. Redis 8.0 added HSETEX and HGETEX, which write or read fields and set their expiry in one command. This lets one hash hold, for example, a session's long-lived settings and a short-lived one-time code, each with its own lifetime.
Uses of TTLs
TTLs bound memory growth for caches, sessions, rate limit windows, locks and temporary tokens. A practical guideline is that any key holding derived, temporary or recomputable data should have a TTL, so that a forgotten cleanup job cannot fill memory over the following months.
Add jitter, a small random amount, to the TTLs of keys that are created together. If a million cache entries all expire in the same second, the backend database receives a million misses at once, which is called a cache stampede. With a base TTL of 3,600 seconds and a random extra of 0 to 300 seconds, the same expiries spread over five minutes.
Lazy Expiration
Redis does not run a timer for each key, because millions of timers would cost far too much. Instead, Redis stores the expiry of each key as an absolute Unix timestamp in milliseconds, and checks it whenever a client accesses the key. If the key has expired, Redis deletes it and behaves as though it never existed. This is lazy expiration, and it costs nothing for keys that no client touches. However, if lazy expiration were the only mechanism, expired keys that nobody reads would stay in memory indefinitely.
Active Expiration
To remove keys that nobody reads, Redis also runs an active expiration cycle. A slow cycle runs as part of the server's periodic task, hz times per second (10 by default), and may use at most 25 percent of CPU time. A fast cycle also runs between turns of the event loop, limited to 1,000 microseconds each time.
Each cycle works through the keys that have a TTL in small batches, checking 20 keys per database in each loop and deleting the ones that have expired. It walks this set of keys incrementally with a cursor, so successive loops continue where the previous one stopped. If more than 10 percent of the checked keys were already expired, the cycle keeps going, because many more expired keys probably remain. Otherwise it stops until the next run.
The result is that expired keys do not disappear at the exact moment they expire. At the default settings, Redis aims to keep expired but not yet deleted keys below about 10 percent of the keys with a TTL. The active-expire-effort setting (1 to 10, default 1) makes the cycle check more keys and accept less waste, at the cost of more CPU.
Consequences of Lazy and Active Expiration
Expired keys can occupy memory for a while, so memory usage can be higher than a precise count of live keys suggests. A very large number of keys expiring at the same moment can cause a latency spike, because deleting them uses main thread time, so spread expiries with jitter. Freeing a big value can also be slow. The lazyfree-lazy-expire setting moves that work to a background thread, but it is off by default in Redis.
Replicas do not expire keys on their own clock. When a key expires on the primary, the primary sends an explicit DEL to its replicas, so all copies stay consistent. Until that DEL arrives, a replica still holds the key in memory but hides it from reads once its expiry time has passed. Because persistence stores expiry times as absolute timestamps, time that an instance spends shut down also counts against each key's life, so keys whose time passed during the shutdown are already expired after a restart.
Common Failure Modes
- Keys without TTLs. The leak appears months later. Audit with SCAN and INFO keyspace.
- TTL lost on overwrite. SET without KEEPTTL silently makes a temporary key permanent.
- Expiry storms. Many keys expiring at once cause latency spikes and stampedes on the backend. Add jitter.
- Big keys. They cause slow deletes, uneven cluster load and blocking. Split them and delete them with UNLINK.
Using This in an Interview
Say that key names are the only namespace in Redis, so they follow one consistent convention, and that every recomputable key gets a TTL with jitter so that memory stays bounded.
Does a key disappear exactly when it expires? No: Redis deletes it on the next access or during a later active cycle, so memory is reclaimed eventually, not at the exact second.
Does overwriting a key keep its TTL? Not with a plain SET, which removes the TTL unless you add KEEPTTL, while in-place commands such as INCR and APPEND keep it.
FAQs
Does Redis delete a key at the exact moment it expires?
No. A key is always treated as gone when it is accessed after its expiry, but its memory is reclaimed either on that access or by a later active cycle. Memory may therefore stay occupied for a short time.
What is the difference between lazy and active expiration?
Lazy expiration checks a key and deletes it only when a client accesses it. Active expiration is a periodic cycle that checks keys with TTLs in small batches and deletes expired ones, which catches keys that nobody reads. Together they keep the cost low and memory bounded.
Key Takeaways
- Key names are your only namespace. Use consistent, colon-separated, versioned names, avoid huge keys, and use SCAN instead of KEYS.
- TTLs bound memory growth. Put them on all recomputable data, add jitter, and remember that SET removes a TTL unless you add KEEPTTL.
- Expiration is lazy plus active. Keys are removed on access or by a time-limited background cycle, so deletion is eventual and memory can briefly exceed the live data.
- Hash fields can expire on their own schedule. HEXPIRE (Redis 7.4) and HSETEX (Redis 8.0) give individual fields a TTL.
Reading Progress
0%
On This Page
Key Naming Conventions
The Role of Key Names
A Practical Convention
TTLs and Expiration
Setting TTLs
How Writes Affect a TTL
Uses of TTLs
Lazy Expiration
Active Expiration
Consequences of Lazy and Active Expiration
Common Failure Modes
Using This in an Interview
FAQs
Key Takeaways