16.3.4. Expiration
Table of Contents
Why Expiration Matters in Redis
Redis is often used for caching, sessions, rate limiting, and many other features that depend on temporary data. Expiration is how Redis automatically deletes keys after some time.
If you set expirations correctly, you avoid:
- Stale cache entries staying forever.
- Session data piling up and consuming memory.
- Rate limit counters persisting long after they are useful.
If you ignore expiration, Redis memory usage can grow without bound and your app might behave incorrectly.
In this chapter we focus on how key expiration works in Redis, how to set and inspect TTLs, and common patterns used in backend development.
Ways to Set Expiration on Keys
Redis lets you set a time to live (TTL) on keys in several ways:
- Set expiration when creating a key.
- Set expiration after a key already exists.
- Refresh expiration by updating the TTL.
- Remove expiration to make a key persistent again.
You can specify expiration in:
- Seconds
- Milliseconds
- As an absolute timestamp in UNIX time.
Below are the main commands and how to use them.
Setting Expiration After Creating a Key
The most basic pattern is:
- Create or update a key.
- Apply expiration.
SET session:user:42 "some session data"
EXPIRE session:user:42 1800 # 1800 seconds = 30 minutes
EXPIRE sets time to live in seconds.
To use milliseconds instead:
PEXPIRE session:user:42 600000 # 600000 ms = 600 s = 10 minutesTo set expiration as an absolute timestamp using UNIX time in seconds:
# expire at a specific timestamp (e.g. 1730000000)
EXPIREAT session:user:42 1730000000For millisecond timestamps:
PEXPIREAT session:user:42 1730000000000Return Values of Expiration Commands
Commands like EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT return:
1if the timeout was set.0if the key does not exist, or the timeout could not be set.
Example:
EXPIRE missing:key 60
(integer) 0Setting Expiration When Creating a Key
Often you want to create a cache entry or a temporary value with an expiration in a single command.
You can use SET with extra options.
SET cache:user:42 '{"name":"Alice"}' EX 300
# EX 300 = expire in 300 secondsSome useful options:
| Option | Meaning | Example |
|---|---|---|
EX seconds | Set expiration in seconds | SET k v EX 60 |
PX milliseconds | Set expiration in ms | SET k v PX 1500 |
NX | Only set if key does not exist | SET k v NX EX 60 |
XX | Only set if key exists | SET k v XX EX 60 |
Combining them for distributed locks or atomic operations:
# Attempt to create a lock with 10 second TTL, only if it does not exist
SET lock:order:123 "random-token" NX EX 10That gives you:
- Key created only once.
- Automatic unlock after 10 seconds even if the app crashes.
Checking and Removing Expiration
Once a key has an expiration, you often want to:
- See how much time is left.
- Remove the expiration to make it permanent again.
Checking TTL of a Key
Use TTL to check remaining life in seconds.
TTL session:user:42Possible results:
| Result | Meaning |
|---|---|
> 0 | Key will expire in that many seconds |
-1 | Key exists but has no expiration (persistent) |
-2 | Key does not exist |
Example walk-through:
SET cache:item:1 "hello" EX 30
TTL cache:item:1
(integer) 27 # for example, after 3 seconds
DEL cache:item:1
TTL cache:item:1
(integer) -2 # key is gone
For milliseconds you can use PTTL:
PTTL cache:item:1
(integer) 25311 # 25.311 seconds remainingRemoving Expiration (Persisting a Key)
If you decide that a key should not expire any more, use PERSIST:
SET temp:data "value" EX 60
TTL temp:data
(integer) 60
PERSIST temp:data
(integer) 1 # expiration removed
TTL temp:data
(integer) -1 # key is now persistent
Return codes of PERSIST:
1expiration removed successfully.0key does not exist, or it had no expiration.
How Redis Actually Expires Keys
Redis expiration is not exact to the millisecond. It tries to be efficient with CPU and memory.
There are two important concepts:
- Passive expiration
When a client accesses an expired key, Redis notices and deletes it on the spot. - Active expiration
Redis periodically scans a sample of keys that have an expiration set and removes the ones that have passed their time.
You do not control these internals directly, but you must understand the implications:
- A key that has passed its expiration time might still be in memory for a short period if no one reads it and the active cycle has not visited it yet.
- If you read it, Redis will treat it as expired and not return stale data.
- From the app perspective, once TTL reaches 0, the key is effectively gone.
Important rule:
You should never rely on exact expiration times down to milliseconds. Treat Redis expiration as approximately correct, not as a precise timer mechanism.
Keyspace Notifications for Expiration
Sometimes backends need to react when a key expires. For example:
- Clear related cache entries.
- Update a database when a temporary lock expires.
- Trigger some cleanup logic.
Redis supports keyspace notifications, which can publish events when keys expire.
To enable expiration events:
CONFIG SET notify-keyspace-events ExHere:
Emeans "Keyevent events" (events about what happened).xmeans "Expired events".
Then you can subscribe to those events (from a client or redis-cli):
PSUBSCRIBE "__keyevent@0__:expired"Example scenario:
- You set expiration on a session key:
SET session:user:42 "..." EX 1800- After 1800 seconds, Redis expires the key.
- Redis publishes a message on the pattern
__keyevent@0__:expiredwith the expired key name.
Client libraries for many languages allow you to use this for event-driven logic, but remember:
- Notifications are best effort, not a guaranteed delivery system.
- They are not a replacement for a proper message queue for critical flows.
Common Expiration Patterns in Backend Development
Redis expiration is central to many backend use cases. Here are several common patterns and concrete examples.
Cache with TTL
A typical pattern: store a computed value for a limited time.
Example: cache user profile details for 5 minutes.
SET cache:user:42 '{"id":42,"name":"Alice"}' EX 300Your backend logic often follows this pattern:
- Try
GET cache:user:42. - If it exists, return the cached JSON.
- If not, load from the database, then store it with
EXand return it.
This keeps the cache bounded in size and prevents old data from living forever.
Sometimes you want shorter TTLs for fast changing data, and longer TTLs for stable data. For example:
| Key | TTL |
|---|---|
cache:user:42 | 300 s |
cache:product:123 | 3600 s |
cache:category:list | 86400 s |
Sessions with Sliding Expiration
User sessions should expire after a period of inactivity, not just from login time.
Imagine a session that should expire after 30 minutes of inactivity. Each time the user performs a request, you refresh the expiration.
Backend logic:
- When user logs in:
SET session:user:42 "session-data" EX 1800- On each request with a valid session:
# Option A: Rewrite value and TTL
SET session:user:42 "session-data" EX 1800
# Option B: Use EXPIRE to refresh TTL
EXPIRE session:user:42 1800The session key will disappear automatically 30 minutes after the last request.
Important rule:
For sliding sessions, always refresh TTL on user activity. If you only set TTL at login, active users will be logged out unexpectedly.
Rate Limiting Using Expiration
Expiration is central to simple rate limiting. For example, limit a user to 100 requests per 60 seconds.
One strategy:
- Key per user and time window.
- Increment and expire.
Example:
INCR rate:user:42
EXPIRE rate:user:42 60In practice, you must make this safe against race conditions. A better version in one step:
# Pseudocode using a Lua script or your language client:
# if key exists:
# INCR
# else:
# SET key 1 EX 60Rough logic:
- First request: key does not exist. Create it with value 1 and
EX 60. - Later requests within 60 seconds: increment. TTL stays around 60 seconds from first request.
- Count > 100 in that period means you reject requests.
After 60 seconds, Redis expires the counter key by itself.
Temporary Tokens and One-Time Codes
For things like:
- Password reset tokens.
- Email verification codes.
- One-time login links.
You want a code that works only for a short time.
Example: password reset token valid for 15 minutes.
SET reset:token:abc123 user:42 EX 900Backend flow:
- When user requests password reset, generate
abc123, store mappingreset:token:abc123 -> user:42withEX 900. - When user clicks the link with
abc123, your backend does:
GET reset:token:abc123- If key exists, token is valid, proceed with reset and delete the key:
DEL reset:token:abc123- If key is missing, TTL expired, or key never existed, reject the request.
Here expiration is your automatic safety net. Tokens cannot be reused after the time window.
Memory Management and Expiration
Expiration and memory policy are related but different.
- Expiration: time based removal of keys.
- Eviction policy: memory pressure based removal when Redis hits memory limits.
If you configure Redis with a memory limit, and use many expiring keys, you need to understand that:
- Keys might be evicted according to the maxmemory-policy even before their TTL ends.
- Expiration does not prevent eviction.
- Expired keys are candidates for deletion during normal operations, and also for eviction processes.
This does not usually break application logic if your code is written to handle missing keys gracefully.
For caching, eviction is usually fine, because a missing key just means "recompute and store again".
Time Units and Timestamps
It is important to be clear about seconds vs milliseconds and about relative vs absolute time.
| Command | Input type | Unit |
|---|---|---|
EXPIRE | Relative time from now | Seconds |
PEXPIRE | Relative time from now | Milliseconds |
EXPIREAT | Absolute UNIX timestamp | Seconds |
PEXPIREAT | Absolute UNIX timestamp | Milliseconds |
TTL | Remaining time | Seconds |
PTTL | Remaining time | Milliseconds |
You can think of relative expiration time as:
$$
t_{\text{expire}} = t_{\text{now}} + \Delta t
$$
Where:
- $t_{\text{now}}$ is the current time.
- $\Delta t$ is the TTL you provide.
Absolute expiration commands directly set $t_{\text{expire}}$ as an absolute UNIX timestamp you choose.
Good Practices for Using Expiration in Backends
A few practical guidelines:
- Always set TTL for cache keys
Caches should not be permanent. Use a sensible TTL based on how often underlying data changes. - Name keys clearly
Include semantics in key names so you know what is temporary. For example:cache:,session:,rate:,reset:. - Handle missing keys as normal
In code, always expect that a key might not exist or might have expired. This should be a normal, non-exceptional path. - Avoid using Redis expiration as a precise scheduler
If you need task scheduling or delayed jobs with guarantees, use a proper job queue or scheduler. Redis expiration is approximate. - Be consistent with time units
Choose whether your project uses seconds or milliseconds for most operations, and stick to it to avoid confusion. - Be cautious when refreshing TTL
For some keys, you want fixed lifetime from creation, not from last access. If you callEXPIREevery time you read the key, you change the semantics to sliding expiration.
By understanding and correctly using expiration, you can keep your Redis instance healthy, control memory usage, and implement many useful backend features such as caching, sessions, rate limiting, and temporary tokens.
Views: 7
KAHIBARO