EXPIRE
EXPIRE key seconds [NX | XX | GT | LT]
- Available since:
- Redis Open Source 1.0.0
- Time complexity:
- O(1)
- ACL categories:
-
@keyspace
,@write
,@fast
,
Set a timeout on key
.
After the timeout has expired, the key will automatically be deleted.
A key with an associated timeout is often said to be volatile in Redis
terminology.
The timeout will only be cleared by commands that delete or overwrite the
contents of the key, including DEL
, SET
, GETSET
and all the *STORE
commands.
This means that all the operations that conceptually alter the value stored at
the key without replacing it with a new one will leave the timeout untouched.
For instance, incrementing the value of a key with INCR
, pushing a new value
into a list with LPUSH
, or altering the field value of a hash with HSET
are
all operations that will leave the timeout untouched.
The timeout can also be cleared, turning the key back into a persistent key,
using the PERSIST
command.
If a key is renamed with RENAME
, the associated time to live is transferred to
the new key name.
If a key is overwritten by RENAME
, like in the case of an existing key Key_A
that is overwritten by a call like RENAME Key_B Key_A
, it does not matter if
the original Key_A
had a timeout associated or not, the new key Key_A
will
inherit all the characteristics of Key_B
.
Note that calling EXPIRE
/PEXPIRE
with a non-positive timeout or
EXPIREAT
/PEXPIREAT
with a time in the past will result in the key being
deleted rather than expired (accordingly, the emitted key event
will be del
, not expired
).
Options
The EXPIRE
command supports a set of options:
NX
-- Set expiry only when the key has no expiryXX
-- Set expiry only when the key has an existing expiryGT
-- Set expiry only when the new expiry is greater than current oneLT
-- Set expiry only when the new expiry is less than current one
A non-volatile key is treated as an infinite TTL for the purpose of GT
and LT
.
The GT
, LT
and NX
options are mutually exclusive.
Refreshing expires
It is possible to call EXPIRE
using as argument a key that already has an
existing expire set.
In this case the time to live of a key is updated to the new value.
There are many useful applications for this, an example is documented in the
Navigation session pattern section below.
Differences in Redis prior 2.1.3
In Redis versions prior 2.1.3 altering a key with an expire set using a command altering its value had the effect of removing the key entirely. This semantics was needed because of limitations in the replication layer that are now fixed.
EXPIRE
would return 0 and not alter the timeout for a key with a timeout set.
Examples
> SET mykey "Hello"
"OK"
> EXPIRE mykey 10
(integer) 1
> TTL mykey
(integer) 10
> SET mykey "Hello World"
"OK"
> TTL mykey
(integer) -1
> EXPIRE mykey 10 XX
(integer) 0
> TTL mykey
(integer) -1
> EXPIRE mykey 10 NX
(integer) 1
> TTL mykey
(integer) 10
import redis
r = redis.Redis(decode_responses=True)
res = r.set("key1", "Hello")
print(res)
# >>> True
res = r.set("key2", "World")
print(res)
# >>> True
res = r.delete("key1", "key2", "key3")
print(res)
# >>> 2
res = r.set("key1", "Hello")
print(res)
# >>> True
res = r.exists("key1")
print(res)
# >>> 1
res = r.exists("nosuchkey")
print(res)
# >>> 0
res = r.set("key2", "World")
print(res)
# >>> True
res = r.exists("key1", "key2", "nosuchkey")
print(res)
# >>> 2
res = r.set("mykey", "Hello")
print(res)
# >>> True
res = r.expire("mykey", 10)
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> 10
res = r.set("mykey", "Hello World")
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> -1
res = r.expire("mykey", 10, xx=True)
print(res)
# >>> False
res = r.ttl("mykey")
print(res)
# >>> -1
res = r.expire("mykey", 10, nx=True)
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> 10
res = r.set("mykey", "Hello")
print(res)
# >>> True
res = r.expire("mykey", 10)
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> 10
res = r.sadd("myset", *set([1, 2, 3, "foo", "foobar", "feelsgood"]))
print(res)
# >>> 6
res = list(r.sscan_iter("myset", match="f*"))
print(res)
# >>> ['foobar', 'foo', 'feelsgood']
cursor, key = r.scan(cursor=0, match='*11*')
print(cursor, key)
cursor, key = r.scan(cursor, match='*11*')
print(cursor, key)
cursor, key = r.scan(cursor, match='*11*')
print(cursor, key)
cursor, key = r.scan(cursor, match='*11*')
print(cursor, key)
cursor, keys = r.scan(cursor, match='*11*', count=1000)
print(cursor, keys)
res = r.geoadd("geokey", (0, 0, "value"))
print(res)
# >>> 1
res = r.zadd("zkey", {"value": 1000})
print(res)
# >>> 1
res = r.type("geokey")
print(res)
# >>> zset
res = r.type("zkey")
print(res)
# >>> zset
cursor, keys = r.scan(cursor=0, _type="zset")
print(keys)
# >>> ['zkey', 'geokey']
res = r.hset("myhash", mapping={"a": 1, "b": 2})
print(res)
# >>> 2
cursor, keys = r.hscan("myhash", 0)
print(keys)
# >>> {'a': '1', 'b': '2'}
cursor, keys = r.hscan("myhash", 0, no_values=True)
print(keys)
# >>> ['a', 'b']
import { createClient } from 'redis';
const client = createClient();
await client.connect().catch(console.error);
const delRes1 = await client.set('key1', 'Hello');
console.log(delRes1); // OK
const delRes2 = await client.set('key2', 'World');
console.log(delRes2); // OK
const delRes3 = await client.del(['key1', 'key2', 'key3']);
console.log(delRes3); // 2
const existsRes1 = await client.set('key1', 'Hello');
console.log(existsRes1); // OK
const existsRes2 = await client.exists('key1');
console.log(existsRes2); // 1
const existsRes3 = await client.exists('nosuchkey');
console.log(existsRes3); // 0
const existsRes4 = await client.set('key2', 'World');
console.log(existsRes4); // OK
const existsRes5 = await client.exists(['key1', 'key2', 'nosuchkey']);
console.log(existsRes5); // 2
const expireRes1 = await client.set('mykey', 'Hello');
console.log(expireRes1); // OK
const expireRes2 = await client.expire('mykey', 10);
console.log(expireRes2); // 1
const expireRes3 = await client.ttl('mykey');
console.log(expireRes3); // 10
const expireRes4 = await client.set('mykey', 'Hello World');
console.log(expireRes4); // OK
const expireRes5 = await client.ttl('mykey');
console.log(expireRes5); // -1
const expireRes6 = await client.expire('mykey', 10, "XX");
console.log(expireRes6); // 0
const expireRes7 = await client.ttl('mykey');
console.log(expireRes7); // -1
const expireRes8 = await client.expire('mykey', 10, "NX");
console.log(expireRes8); // 1
const expireRes9 = await client.ttl('mykey');
console.log(expireRes9); // 10
const ttlRes1 = await client.set('mykey', 'Hello');
console.log(ttlRes1); // OK
const ttlRes2 = await client.expire('mykey', 10);
console.log(ttlRes2); // 1
const ttlRes3 = await client.ttl('mykey');
console.log(ttlRes3); // 10
const scan1Res1 = await client.sAdd('myset', ['1', '2', '3', 'foo', 'foobar', 'feelsgood']);
console.log(scan1Res1); // 6
let scan1Res2 = [];
for await (const values of client.sScanIterator('myset', { MATCH: 'f*' })) {
scan1Res2 = scan1Res2.concat(values);
}
console.log(scan1Res2); // ['foo', 'foobar', 'feelsgood']
let cursor = '0';
let scanResult;
scanResult = await client.scan(cursor, { MATCH: '*11*' });
console.log(scanResult.cursor, scanResult.keys);
scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*' });
console.log(scanResult.cursor, scanResult.keys);
scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*' });
console.log(scanResult.cursor, scanResult.keys);
scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*' });
console.log(scanResult.cursor, scanResult.keys);
scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*', COUNT: 1000 });
console.log(scanResult.cursor, scanResult.keys);
const scan3Res1 = await client.geoAdd('geokey', { longitude: 0, latitude: 0, member: 'value' });
console.log(scan3Res1); // 1
const scan3Res2 = await client.zAdd('zkey', [{ score: 1000, value: 'value' }]);
console.log(scan3Res2); // 1
const scan3Res3 = await client.type('geokey');
console.log(scan3Res3); // zset
const scan3Res4 = await client.type('zkey');
console.log(scan3Res4); // zset
const scan3Res5 = await client.scan('0', { TYPE: 'zset' });
console.log(scan3Res5.keys); // ['zkey', 'geokey']
const scan4Res1 = await client.hSet('myhash', { a: 1, b: 2 });
console.log(scan4Res1); // 2
const scan4Res2 = await client.hScan('myhash', '0');
console.log(scan4Res2.entries); // [{field: 'a', value: '1'}, {field: 'b', value: '2'}]
const scan4Res3 = await client.hScan('myhash', '0', { COUNT: 10 });
const items = scan4Res3.entries.map((item) => item.field)
console.log(items); // ['a', 'b']
await client.close();
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.args.ExpiryOption;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CmdsGenericExample {
public void run() {
UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379");
String delResult1 = jedis.set("key1", "Hello");
System.out.println(delResult1); // >>> OK
String delResult2 = jedis.set("key2", "World");
System.out.println(delResult2); // >>> OK
long delResult3 = jedis.del("key1", "key2", "key3");
System.out.println(delResult3); // >>> 2
// Tests for 'del' step.
String existsResult1 = jedis.set("key1", "Hello");
System.out.println(existsResult1); // >>> OK
boolean existsResult2 = jedis.exists("key1");
System.out.println(existsResult2); // >>> true
boolean existsResult3 = jedis.exists("nosuchkey");
System.out.println(existsResult3); // >>> false
String existsResult4 = jedis.set("key2", "World");
System.out.println(existsResult4); // >>> OK
long existsResult5 = jedis.exists("key1", "key2", "nosuchkey");
System.out.println(existsResult5); // >>> 2
// Tests for 'exists' step.
String expireResult1 = jedis.set("mykey", "Hello");
System.out.println(expireResult1); // >>> OK
long expireResult2 = jedis.expire("mykey", 10);
System.out.println(expireResult2); // >>> 1
long expireResult3 = jedis.ttl("mykey");
System.out.println(expireResult3); // >>> 10
String expireResult4 = jedis.set("mykey", "Hello World");
System.out.println(expireResult4); // >>> OK
long expireResult5 = jedis.ttl("mykey");
System.out.println(expireResult5); // >>> -1
long expireResult6 = jedis.expire("mykey", 10, ExpiryOption.XX);
System.out.println(expireResult6); // >>> 0
long expireResult7 = jedis.ttl("mykey");
System.out.println(expireResult7); // >>> -1
long expireResult8 = jedis.expire("mykey", 10, ExpiryOption.NX);
System.out.println(expireResult8); // >>> 1
long expireResult9 = jedis.ttl("mykey");
System.out.println(expireResult9); // >>> 10
// Tests for 'expire' step.
String ttlResult1 = jedis.set("mykey", "Hello");
System.out.println(ttlResult1); // >>> OK
long ttlResult2 = jedis.expire("mykey", 10);
System.out.println(ttlResult2); // >>> 1
long ttlResult3 = jedis.ttl("mykey");
System.out.println(ttlResult3); // >>> 10
// Tests for 'ttl' step.
jedis.close();
}
}
package io.redis.examples.async;
import io.lettuce.core.*;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.StatefulRedisConnection;
import java.util.concurrent.CompletableFuture;
public class CmdsGenericExample {
public void run() {
CompletableFuture<Void> existsExample = asyncCommands.set("key1", "Hello").thenCompose(res1 -> {
System.out.println(res1); // >>> OK
return asyncCommands.exists("key1");
}).thenCompose(res2 -> {
System.out.println(res2); // >>> 1
return asyncCommands.exists("nosuchkey");
}).thenCompose(res3 -> {
System.out.println(res3); // >>> 0
return asyncCommands.set("key2", "World");
}).thenCompose(res4 -> {
System.out.println(res4); // >>> OK
return asyncCommands.exists("key1", "key2", "nosuchkey");
}).thenAccept(res5 -> {
System.out.println(res5); // >>> 2
}).toCompletableFuture();
existsExample.join();
} finally {
redisClient.shutdown();
}
}
}
package io.redis.examples.reactive;
import io.lettuce.core.*;
import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.api.StatefulRedisConnection;
import reactor.core.publisher.Mono;
public class CmdsGenericExample {
public void run() {
RedisClient redisClient = RedisClient.create("redis://localhost:6379");
try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
RedisReactiveCommands<String, String> reactiveCommands = connection.reactive();
Mono<Void> existsExample = reactiveCommands.set("key1", "Hello").doOnNext(res1 -> {
System.out.println(res1); // >>> OK
}).then(reactiveCommands.exists("key1")).doOnNext(res2 -> {
System.out.println(res2); // >>> 1
}).then(reactiveCommands.exists("nosuchkey")).doOnNext(res3 -> {
System.out.println(res3); // >>> 0
}).then(reactiveCommands.set("key2", "World")).doOnNext(res4 -> {
System.out.println(res4); // >>> OK
}).then(reactiveCommands.exists("key1", "key2", "nosuchkey")).doOnNext(res5 -> {
System.out.println(res5); // >>> 2
}).then();
Mono.when(existsExample).block();
} finally {
redisClient.shutdown();
}
}
}
package example_commands_test
import (
"context"
"fmt"
"math"
"time"
"github.com/redis/go-redis/v9"
)
func ExampleClient_del_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
delResult1, err := rdb.Set(ctx, "key1", "Hello", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(delResult1) // >>> OK
delResult2, err := rdb.Set(ctx, "key2", "World", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(delResult2) // >>> OK
delResult3, err := rdb.Del(ctx, "key1", "key2", "key3").Result()
if err != nil {
panic(err)
}
fmt.Println(delResult3) // >>> 2
}
func ExampleClient_exists_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
existsResult1, err := rdb.Set(ctx, "key1", "Hello", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult1) // >>> OK
existsResult2, err := rdb.Exists(ctx, "key1").Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult2) // >>> 1
existsResult3, err := rdb.Exists(ctx, "nosuchkey").Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult3) // >>> 0
existsResult4, err := rdb.Set(ctx, "key2", "World", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult4) // >>> OK
existsResult5, err := rdb.Exists(ctx, "key1", "key2", "nosuchkey").Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult5) // >>> 2
}
func ExampleClient_expire_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
expireResult1, err := rdb.Set(ctx, "mykey", "Hello", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult1) // >>> OK
expireResult2, err := rdb.Expire(ctx, "mykey", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult2) // >>> true
expireResult3, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(math.Round(expireResult3.Seconds())) // >>> 10
expireResult4, err := rdb.Set(ctx, "mykey", "Hello World", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult4) // >>> OK
expireResult5, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult5) // >>> -1ns
expireResult6, err := rdb.ExpireXX(ctx, "mykey", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult6) // >>> false
expireResult7, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult7) // >>> -1ns
expireResult8, err := rdb.ExpireNX(ctx, "mykey", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult8) // >>> true
expireResult9, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(math.Round(expireResult9.Seconds())) // >>> 10
}
func ExampleClient_ttl_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
ttlResult1, err := rdb.Set(ctx, "mykey", "Hello", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(ttlResult1) // >>> OK
ttlResult2, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(math.Round(ttlResult2.Seconds())) // >>> 10
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class CmdsGenericExample
{
public void Run()
{
var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();
// Tests for 'copy' step.
bool delResult1 = db.StringSet("key1", "Hello");
Console.WriteLine(delResult1); // >>> true
bool delResult2 = db.StringSet("key2", "World");
Console.WriteLine(delResult2); // >>> true
long delResult3 = db.KeyDelete(["key1", "key2", "key3"]);
Console.WriteLine(delResult3); // >>> 2
// Tests for 'del' step.
// Tests for 'dump' step.
bool existsResult1 = db.StringSet("key1", "Hello");
Console.WriteLine(existsResult1); // >>> true
bool existsResult2 = db.KeyExists("key1");
Console.WriteLine(existsResult2); // >>> true
bool existsResult3 = db.KeyExists("nosuchkey");
Console.WriteLine(existsResult3); // >>> false
bool existsResult4 = db.StringSet("key2", "World");
Console.WriteLine(existsResult4); // >>> true
long existsResult5 = db.KeyExists(["key1", "key2", "nosuchkey"]);
Console.WriteLine(existsResult5); // >>> 2
// Tests for 'exists' step.
bool expireResult1 = db.StringSet("mykey", "Hello");
Console.WriteLine(expireResult1); // >>> true
bool expireResult2 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10));
Console.WriteLine(expireResult2); // >>> true
TimeSpan expireResult3 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult3.TotalSeconds)); // >>> 10
bool expireResult4 = db.StringSet("mykey", "Hello World");
Console.WriteLine(expireResult4); // >>> true
TimeSpan expireResult5 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult5.TotalSeconds).ToString()); // >>> 0
bool expireResult6 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10), ExpireWhen.HasExpiry);
Console.WriteLine(expireResult6); // >>> false
TimeSpan expireResult7 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult7.TotalSeconds)); // >>> 0
bool expireResult8 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10), ExpireWhen.HasNoExpiry);
Console.WriteLine(expireResult8); // >>> true
TimeSpan expireResult9 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult9.TotalSeconds)); // >>> 10
// Tests for 'expire' step.
// Tests for 'expireat' step.
// Tests for 'expiretime' step.
// Tests for 'keys' step.
// Tests for 'migrate' step.
// Tests for 'move' step.
// Tests for 'object_encoding' step.
// Tests for 'object_freq' step.
// Tests for 'object_idletime' step.
// Tests for 'object_refcount' step.
// Tests for 'persist' step.
// Tests for 'pexpire' step.
// Tests for 'pexpireat' step.
// Tests for 'pexpiretime' step.
// Tests for 'pttl' step.
// Tests for 'randomkey' step.
// Tests for 'rename' step.
// Tests for 'renamenx' step.
// Tests for 'restore' step.
// Tests for 'scan1' step.
// Tests for 'scan2' step.
// Tests for 'scan3' step.
// Tests for 'scan4' step.
// Tests for 'sort' step.
// Tests for 'sort_ro' step.
// Tests for 'touch' step.
bool ttlResult1 = db.StringSet("mykey", "Hello");
Console.WriteLine(ttlResult1); // >>> true
bool ttlResult2 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10));
Console.WriteLine(ttlResult2);
TimeSpan ttlResult3 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
string ttlRes = Math.Round(ttlResult3.TotalSeconds).ToString();
Console.WriteLine(Math.Round(ttlResult3.TotalSeconds)); // >>> 10
// Tests for 'ttl' step.
// Tests for 'type' step.
// Tests for 'unlink' step.
// Tests for 'wait' step.
// Tests for 'waitaof' step.
}
}
<?php
require_once 'vendor/autoload.php';
use Predis\Client as PredisClient;
class CmdsGenericTest
{
public function testCmdsGeneric() {
$r = new PredisClient([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'database' => 0,
]);
$existsResult1 = $r->set('key1', 'Hello');
echo $existsResult1 . PHP_EOL; // >>> OK
$existsResult2 = $r->exists('key1');
echo $existsResult2 . PHP_EOL; // >>> 1
$existsResult3 = $r->exists('nosuchkey');
echo $existsResult3 . PHP_EOL; // >>> 0
$existsResult4 = $r->set('key2', 'World');
echo $existsResult4 . PHP_EOL; // >>> OK
$existsResult5 = $r->exists('key1', 'key2', 'nosuchkey');
echo $existsResult5 . PHP_EOL; // >>> 2
}
}
Give these commands a try in the interactive console:
Pattern: Navigation session
Imagine you have a web service and you are interested in the latest N pages recently visited by your users, such that each adjacent page view was not performed more than 60 seconds after the previous. Conceptually you may consider this set of page views as a Navigation session of your user, that may contain interesting information about what kind of products he or she is looking for currently, so that you can recommend related products.
You can easily model this pattern in Redis using the following strategy: every time the user does a page view you call the following commands:
MULTI
RPUSH pagewviews.user:<userid> http://.....
EXPIRE pagewviews.user:<userid> 60
EXEC
If the user will be idle more than 60 seconds, the key will be deleted and only subsequent page views that have less than 60 seconds of difference will be recorded.
This pattern is easily modified to use counters using INCR
instead of lists
using RPUSH
.
Appendix: Redis expires
Keys with an expire
Normally Redis keys are created without an associated time to live.
The key will simply live forever, unless it is removed by the user in an
explicit way, for instance using the DEL
command.
The EXPIRE
family of commands is able to associate an expire to a given key,
at the cost of some additional memory used by the key.
When a key has an expire set, Redis will make sure to remove the key when the
specified amount of time elapsed.
The key time to live can be updated or entirely removed using the EXPIRE
and
PERSIST
command (or other strictly related commands).
Expire accuracy
In Redis 2.4 the expire might not be pin-point accurate, and it could be between zero to one seconds out.
Since Redis 2.6 the expire error is from 0 to 1 milliseconds.
Expires and persistence
Keys expiring information is stored as absolute Unix timestamps (in milliseconds in case of Redis version 2.6 or greater). This means that the time is flowing even when the Redis instance is not active.
For expires to work well, the computer time must be taken stable. If you move an RDB file from two computers with a big desync in their clocks, funny things may happen (like all the keys loaded to be expired at loading time).
Even running instances will always check the computer clock, so for instance if you set a key with a time to live of 1000 seconds, and then set your computer time 2000 seconds in the future, the key will be expired immediately, instead of lasting for 1000 seconds.
How Redis expires keys
Redis keys are expired in two ways: a passive way and an active way.
A key is passively expired when a client tries to access it and the key is timed out.
However, this is not enough as there are expired keys that will never be accessed again. These keys should be expired anyway, so periodically, Redis tests a few keys at random amongst the set of keys with an expiration. All the keys that are already expired are deleted from the keyspace.
How expires are handled in the replication link and AOF file
In order to obtain a correct behavior without sacrificing consistency, when a
key expires, a DEL
operation is synthesized in both the AOF file and gains all
the attached replicas nodes.
This way the expiration process is centralized in the master instance, and there
is no chance of consistency errors.
However while the replicas connected to a master will not expire keys
independently (but will wait for the DEL
coming from the master), they'll
still take the full state of the expires existing in the dataset, so when a
replica is elected to master it will be able to expire the keys independently,
fully acting as a master.
Redis Query Engine and expiration
Starting with Redis 8, the Redis Query Engine has enhanced behavior when handling expiring keys. For detailed information about how FT.SEARCH
and FT.AGGREGATE
commands interact with expiring keys, see Key and field expiration behavior.
Return information
One of the following:
- Integer reply:
0
if the timeout was not set; for example, the key doesn't exist, or the operation was skipped because of the provided arguments. - Integer reply:
1
if the timeout was set.
History
- Starting with Redis version 7.0.0: Added options:
NX
,XX
,GT
andLT
.