mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-23 08:38:27 -05:00
0367a80819
This commit introduces two new command and two options for an existing command GETEX <key> [PERSIST][EX seconds][PX milliseconds] [EXAT seconds-timestamp] [PXAT milliseconds-timestamp] The getexCommand() function implements extended options and variants of the GET command. Unlike GET command this command is not read-only. Only one of the options can be used at a given time. 1. PERSIST removes any TTL associated with the key. 2. EX Set expiry TTL in seconds. 3. PX Set expiry TTL in milliseconds. 4. EXAT Same like EX instead of specifying the number of seconds representing the TTL (time to live), it takes an absolute Unix timestamp 5. PXAT Same like PX instead of specifying the number of milliseconds representing the TTL (time to live), it takes an absolute Unix timestamp Command would return either the bulk string, error or nil. GETDEL <key> Would delete the key after getting. SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>] [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] Two new options added here are EXAT and PXAT Key implementation notes - `SET` with `PX/EX/EXAT/PXAT` is always translated to `PXAT` in `AOF`. When relative time is specified (`PX/EX`), replication will always use `PX`. - `setexCommand` and `psetexCommand` would no longer need translation in `feedAppendOnlyFile` as they are modified to invoke `setGenericCommand ` with appropriate flags which will take care of correct AOF translation. - `GETEX` without any optional argument behaves like `GET`. - `GETEX` command is never propagated, It is either propagated as `PEXPIRE[AT], or PERSIST`. - `GETDEL` command is propagated as `DEL` - Combined the validation for `SET` and `GETEX` arguments. - Test cases to validate AOF/Replication propagation
360 lines
11 KiB
Tcl
360 lines
11 KiB
Tcl
start_server {tags {"expire"}} {
|
|
test {EXPIRE - set timeouts multiple times} {
|
|
r set x foobar
|
|
set v1 [r expire x 5]
|
|
set v2 [r ttl x]
|
|
set v3 [r expire x 10]
|
|
set v4 [r ttl x]
|
|
r expire x 2
|
|
list $v1 $v2 $v3 $v4
|
|
} {1 [45] 1 10}
|
|
|
|
test {EXPIRE - It should be still possible to read 'x'} {
|
|
r get x
|
|
} {foobar}
|
|
|
|
tags {"slow"} {
|
|
test {EXPIRE - After 2.1 seconds the key should no longer be here} {
|
|
after 2100
|
|
list [r get x] [r exists x]
|
|
} {{} 0}
|
|
}
|
|
|
|
test {EXPIRE - write on expire should work} {
|
|
r del x
|
|
r lpush x foo
|
|
r expire x 1000
|
|
r lpush x bar
|
|
r lrange x 0 -1
|
|
} {bar foo}
|
|
|
|
test {EXPIREAT - Check for EXPIRE alike behavior} {
|
|
r del x
|
|
r set x foo
|
|
r expireat x [expr [clock seconds]+15]
|
|
r ttl x
|
|
} {1[345]}
|
|
|
|
test {SETEX - Set + Expire combo operation. Check for TTL} {
|
|
r setex x 12 test
|
|
r ttl x
|
|
} {1[012]}
|
|
|
|
test {SETEX - Check value} {
|
|
r get x
|
|
} {test}
|
|
|
|
test {SETEX - Overwrite old key} {
|
|
r setex y 1 foo
|
|
r get y
|
|
} {foo}
|
|
|
|
tags {"slow"} {
|
|
test {SETEX - Wait for the key to expire} {
|
|
after 1100
|
|
r get y
|
|
} {}
|
|
}
|
|
|
|
test {SETEX - Wrong time parameter} {
|
|
catch {r setex z -10 foo} e
|
|
set _ $e
|
|
} {*invalid expire*}
|
|
|
|
test {PERSIST can undo an EXPIRE} {
|
|
r set x foo
|
|
r expire x 50
|
|
list [r ttl x] [r persist x] [r ttl x] [r get x]
|
|
} {50 1 -1 foo}
|
|
|
|
test {PERSIST returns 0 against non existing or non volatile keys} {
|
|
r set x foo
|
|
list [r persist foo] [r persist nokeyatall]
|
|
} {0 0}
|
|
|
|
test {EXPIRE precision is now the millisecond} {
|
|
# This test is very likely to do a false positive if the
|
|
# server is under pressure, so if it does not work give it a few more
|
|
# chances.
|
|
for {set j 0} {$j < 3} {incr j} {
|
|
r del x
|
|
r setex x 1 somevalue
|
|
after 900
|
|
set a [r get x]
|
|
after 1100
|
|
set b [r get x]
|
|
if {$a eq {somevalue} && $b eq {}} break
|
|
}
|
|
list $a $b
|
|
} {somevalue {}}
|
|
|
|
test {PEXPIRE/PSETEX/PEXPIREAT can set sub-second expires} {
|
|
# This test is very likely to do a false positive if the
|
|
# server is under pressure, so if it does not work give it a few more
|
|
# chances.
|
|
for {set j 0} {$j < 30} {incr j} {
|
|
r del x y z
|
|
r psetex x 100 somevalue
|
|
after 80
|
|
set a [r get x]
|
|
after 120
|
|
set b [r get x]
|
|
|
|
r set x somevalue
|
|
r pexpire x 100
|
|
after 80
|
|
set c [r get x]
|
|
after 120
|
|
set d [r get x]
|
|
|
|
r set x somevalue
|
|
set now [r time]
|
|
r pexpireat x [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+200]
|
|
after 20
|
|
set e [r get x]
|
|
after 220
|
|
set f [r get x]
|
|
|
|
if {$a eq {somevalue} && $b eq {} &&
|
|
$c eq {somevalue} && $d eq {} &&
|
|
$e eq {somevalue} && $f eq {}} break
|
|
}
|
|
if {$::verbose} {
|
|
puts "sub-second expire test attempts: $j"
|
|
}
|
|
list $a $b $c $d $e $f
|
|
} {somevalue {} somevalue {} somevalue {}}
|
|
|
|
test {TTL returns time to live in seconds} {
|
|
r del x
|
|
r setex x 10 somevalue
|
|
set ttl [r ttl x]
|
|
assert {$ttl > 8 && $ttl <= 10}
|
|
}
|
|
|
|
test {PTTL returns time to live in milliseconds} {
|
|
r del x
|
|
r setex x 1 somevalue
|
|
set ttl [r pttl x]
|
|
assert {$ttl > 900 && $ttl <= 1000}
|
|
}
|
|
|
|
test {TTL / PTTL return -1 if key has no expire} {
|
|
r del x
|
|
r set x hello
|
|
list [r ttl x] [r pttl x]
|
|
} {-1 -1}
|
|
|
|
test {TTL / PTTL return -2 if key does not exit} {
|
|
r del x
|
|
list [r ttl x] [r pttl x]
|
|
} {-2 -2}
|
|
|
|
test {Redis should actively expire keys incrementally} {
|
|
r flushdb
|
|
r psetex key1 500 a
|
|
r psetex key2 500 a
|
|
r psetex key3 500 a
|
|
set size1 [r dbsize]
|
|
# Redis expires random keys ten times every second so we are
|
|
# fairly sure that all the three keys should be evicted after
|
|
# one second.
|
|
after 1000
|
|
set size2 [r dbsize]
|
|
list $size1 $size2
|
|
} {3 0}
|
|
|
|
test {Redis should lazy expire keys} {
|
|
r flushdb
|
|
r debug set-active-expire 0
|
|
r psetex key1 500 a
|
|
r psetex key2 500 a
|
|
r psetex key3 500 a
|
|
set size1 [r dbsize]
|
|
# Redis expires random keys ten times every second so we are
|
|
# fairly sure that all the three keys should be evicted after
|
|
# one second.
|
|
after 1000
|
|
set size2 [r dbsize]
|
|
r mget key1 key2 key3
|
|
set size3 [r dbsize]
|
|
r debug set-active-expire 1
|
|
list $size1 $size2 $size3
|
|
} {3 3 0}
|
|
|
|
test {EXPIRE should not resurrect keys (issue #1026)} {
|
|
r debug set-active-expire 0
|
|
r set foo bar
|
|
r pexpire foo 500
|
|
after 1000
|
|
r expire foo 10
|
|
r debug set-active-expire 1
|
|
r exists foo
|
|
} {0}
|
|
|
|
test {5 keys in, 5 keys out} {
|
|
r flushdb
|
|
r set a c
|
|
r expire a 5
|
|
r set t c
|
|
r set e c
|
|
r set s c
|
|
r set foo b
|
|
lsort [r keys *]
|
|
} {a e foo s t}
|
|
|
|
test {EXPIRE with empty string as TTL should report an error} {
|
|
r set foo bar
|
|
catch {r expire foo ""} e
|
|
set e
|
|
} {*not an integer*}
|
|
|
|
test {EXPIRE and SET/GETEX EX/PX/EXAT/PXAT option, TTL should not be reset after loadaof} {
|
|
# This test makes sure that expire times are propagated as absolute
|
|
# times to the AOF file and not as relative time, so that when the AOF
|
|
# is reloaded the TTLs are not being shifted forward to the future.
|
|
# We want the time to logically pass when the server is restarted!
|
|
|
|
r config set appendonly yes
|
|
r set foo1 bar EX 100
|
|
r set foo2 bar PX 100000
|
|
r set foo3 bar
|
|
r set foo4 bar
|
|
r expire foo3 100
|
|
r pexpire foo4 100000
|
|
r setex foo5 100 bar
|
|
r psetex foo6 100000 bar
|
|
r set foo7 bar EXAT [expr [clock seconds] + 100]
|
|
r set foo8 bar PXAT [expr [clock milliseconds] + 100000]
|
|
r set foo9 bar
|
|
r getex foo9 EX 100
|
|
r set foo10 bar
|
|
r getex foo10 PX 100000
|
|
r set foo11 bar
|
|
r getex foo11 EXAT [expr [clock seconds] + 100]
|
|
r set foo12 bar
|
|
r getex foo12 PXAT [expr [clock milliseconds] + 100000]
|
|
|
|
after 2000
|
|
r debug loadaof
|
|
assert_range [r ttl foo1] 90 98
|
|
assert_range [r ttl foo2] 90 98
|
|
assert_range [r ttl foo3] 90 98
|
|
assert_range [r ttl foo4] 90 98
|
|
assert_range [r ttl foo5] 90 98
|
|
assert_range [r ttl foo6] 90 98
|
|
assert_range [r ttl foo7] 90 98
|
|
assert_range [r ttl foo8] 90 98
|
|
assert_range [r ttl foo9] 90 98
|
|
assert_range [r ttl foo10] 90 98
|
|
assert_range [r ttl foo11] 90 98
|
|
assert_range [r ttl foo12] 90 98
|
|
}
|
|
|
|
test {EXPIRE relative and absolute propagation to replicas} {
|
|
# Make sure that relative and absolute expire commands are propagated
|
|
# "as is" to replicas.
|
|
# We want replicas to honor the same high level contract of expires that
|
|
# the master has, that is, we want the time to be counted logically
|
|
# starting from the moment the write was received. This usually provides
|
|
# the most coherent behavior from the point of view of the external
|
|
# users, with TTLs that are similar from the POV of the external observer.
|
|
#
|
|
# This test is here to stop some innocent / eager optimization or cleanup
|
|
# from doing the wrong thing without proper discussion, see:
|
|
# https://github.com/redis/redis/pull/5171#issuecomment-409553266
|
|
|
|
set repl [attach_to_replication_stream]
|
|
r set foo1 bar ex 200
|
|
r set foo1 bar px 100000
|
|
r set foo1 bar exat [expr [clock seconds]+100]
|
|
r set foo1 bar pxat [expr [clock milliseconds]+10000]
|
|
r setex foo1 100 bar
|
|
r psetex foo1 100000 bar
|
|
r set foo2 bar
|
|
r expire foo2 100
|
|
r pexpire foo2 100000
|
|
r set foo3 bar
|
|
r expireat foo3 [expr [clock seconds]+100]
|
|
r pexpireat foo3 [expr [clock seconds]*1000+100000]
|
|
r expireat foo3 [expr [clock seconds]-100]
|
|
r set foo4 bar
|
|
r getex foo4 ex 200
|
|
r getex foo4 px 200000
|
|
r getex foo4 exat [expr [clock seconds]+100]
|
|
r getex foo4 pxat [expr [clock milliseconds]+10000]
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{set foo1 bar PX 200000}
|
|
{set foo1 bar PX 100000}
|
|
{set foo1 bar PXAT *}
|
|
{set foo1 bar PXAT *}
|
|
{set foo1 bar PX 100000}
|
|
{set foo1 bar PX 100000}
|
|
{set foo2 bar}
|
|
{expire foo2 100}
|
|
{pexpire foo2 100000}
|
|
{set foo3 bar}
|
|
{expireat foo3 *}
|
|
{pexpireat foo3 *}
|
|
{del foo3}
|
|
{set foo4 bar}
|
|
{pexpire foo4 200000}
|
|
{pexpire foo4 200000}
|
|
{pexpireat foo4 *}
|
|
{pexpireat foo4 *}
|
|
}
|
|
}
|
|
|
|
test {SET command will remove expire} {
|
|
r set foo bar EX 100
|
|
r set foo bar
|
|
r ttl foo
|
|
} {-1}
|
|
|
|
test {SET - use KEEPTTL option, TTL should not be removed} {
|
|
r set foo bar EX 100
|
|
r set foo bar KEEPTTL
|
|
set ttl [r ttl foo]
|
|
assert {$ttl <= 100 && $ttl > 90}
|
|
}
|
|
|
|
test {SET - use KEEPTTL option, TTL should not be removed after loadaof} {
|
|
r config set appendonly yes
|
|
r set foo bar EX 100
|
|
r set foo bar2 KEEPTTL
|
|
after 2000
|
|
r debug loadaof
|
|
set ttl [r ttl foo]
|
|
assert {$ttl <= 98 && $ttl > 90}
|
|
}
|
|
|
|
test {GETEX use of PERSIST option should remove TTL} {
|
|
r set foo bar EX 100
|
|
r getex foo PERSIST
|
|
r ttl foo
|
|
} {-1}
|
|
|
|
test {GETEX use of PERSIST option should remove TTL after loadaof} {
|
|
r set foo bar EX 100
|
|
r getex foo PERSIST
|
|
after 2000
|
|
r debug loadaof
|
|
r ttl foo
|
|
} {-1}
|
|
|
|
test {GETEX propagate as to replica as PERSIST, DEL, or nothing} {
|
|
set repl [attach_to_replication_stream]
|
|
r set foo bar EX 100
|
|
r getex foo PERSIST
|
|
r getex foo
|
|
r getex foo exat [expr [clock seconds]-100]
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{set foo bar PX 100000}
|
|
{persist foo}
|
|
{del foo}
|
|
}
|
|
}
|
|
}
|