Adapt HRANDFIELD to HFE feature (#13348)
Considerations for the selected imp of HRANDFIELD & HFE feature: HRANDFIELD might access any of the fields in the hash as some of them might be expired. And so the Implementation of HRANDFIELD along with HFEs might be one of the two options: 1. Expire hash-fields before diving into handling HRANDFIELD. 2. Refine HRANDFIELD cases to deal with expired fields. Regarding the first option, as reference, the command RANDOMKEY also declareson O(1) complexity, yet might be stuck on a very long (but not infinite) loop trying to find non-expired keys. Furthermore RANDOMKEY also evicts expired keys along the way even though it is categorized as a read-only command. Note that the case of HRANDFIELD is more lightweight versus RANDOMKEY since HFEs have much more effective and aggressive active-expiration for fields behind. The second option introduces additional implementation complexity to HRANDFIELD. We could further refine HRANDFIELD cases to differentiate between scenarios with many expired fields versus few expired fields, and adjust based on the percentage of expired fields. However, this approach could still lead to long loops or necessitate expiring fields before selecting them. For the “lightweight” cases it is also expected to have a lightweight expiration. Considering the pros and cons, and the fact that HRANDFIELD is an infrequent command (particularly with HFEs) and the fact we have effective active-expiration behind for hash-fields, it is better to keep it simple and choose option number 1. Other changes: * Don't mark command dirty by internal hashTypeExpire(). It causes to read only command of HRANDFIELD to be accidently propagated (This flag should be indicated at higher level, by the command functions). * Align `hashTypeExpireIfNeeded()` and `hashTypeGetValue()` to be more aligned with `expireIfNeeded()` logic of keyspace.
This commit is contained in:
@@ -32,13 +32,6 @@ proc get_hashes_with_expiry_fields {r} {
|
||||
return 0
|
||||
}
|
||||
|
||||
proc create_hash {key entries} {
|
||||
r del $key
|
||||
foreach entry $entries {
|
||||
r hset $key [lindex $entry 0] [lindex $entry 1]
|
||||
}
|
||||
}
|
||||
|
||||
proc get_keys {l} {
|
||||
set res {}
|
||||
foreach entry $l {
|
||||
@@ -48,22 +41,6 @@ proc get_keys {l} {
|
||||
return $res
|
||||
}
|
||||
|
||||
proc cmp_hrandfield_result {hash_name expected_result} {
|
||||
# Accumulate hrandfield results
|
||||
unset -nocomplain myhash
|
||||
array set myhash {}
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
set key [r hrandfield $hash_name]
|
||||
set myhash($key) 1
|
||||
}
|
||||
set res [lsort [array names myhash]]
|
||||
if {$res eq $expected_result} {
|
||||
return 1
|
||||
} else {
|
||||
return $res
|
||||
}
|
||||
}
|
||||
|
||||
proc dumpAllHashes {client} {
|
||||
set keyAndFields(0,0) 0
|
||||
unset keyAndFields
|
||||
@@ -77,36 +54,6 @@ proc dumpAllHashes {client} {
|
||||
return [array get keyAndFields]
|
||||
}
|
||||
|
||||
proc hrandfieldTest {activeExpireConfig} {
|
||||
r debug set-active-expire $activeExpireConfig
|
||||
r del myhash
|
||||
set contents {{field1 1} {field2 2} }
|
||||
create_hash myhash $contents
|
||||
|
||||
set factorValgrind [expr {$::valgrind ? 2 : 1}]
|
||||
|
||||
# Set expiration time for field1 and field2 such that field1 expires first
|
||||
r hpexpire myhash 1 NX FIELDS 1 field1
|
||||
r hpexpire myhash 100 NX FIELDS 1 field2
|
||||
|
||||
# On call hrandfield command lazy expire deletes field1 first
|
||||
wait_for_condition 8 10 {
|
||||
[cmp_hrandfield_result myhash "field2"] == 1
|
||||
} else {
|
||||
fail "Expected field2 to be returned by HRANDFIELD."
|
||||
}
|
||||
|
||||
# On call hrandfield command lazy expire deletes field2 as well
|
||||
wait_for_condition 8 20 {
|
||||
[cmp_hrandfield_result myhash "{}"] == 1
|
||||
} else {
|
||||
fail "Expected {} to be returned by HRANDFIELD."
|
||||
}
|
||||
|
||||
# restore the default value
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
############################### TESTS #########################################
|
||||
|
||||
start_server {tags {"external:skip needs:debug"}} {
|
||||
@@ -396,22 +343,33 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
# OPEN: To decide if to delete expired fields at start of HRANDFIELD.
|
||||
# test "Test HRANDFIELD does not return expired fields ($type)" {
|
||||
# hrandfieldTest 0
|
||||
# hrandfieldTest 1
|
||||
# }
|
||||
|
||||
test "Test HRANDFIELD can return expired fields ($type)" {
|
||||
test "Test HRANDFIELD deletes all expired fields ($type)" {
|
||||
r debug set-active-expire 0
|
||||
r del myhash
|
||||
r flushall
|
||||
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
|
||||
r hpexpire myhash 1 NX FIELDS 4 f1 f2 f3 f4
|
||||
r hpexpire myhash 1 FIELDS 2 f1 f2
|
||||
after 5
|
||||
set res [cmp_hrandfield_result myhash "f1 f2 f3 f4 f5"]
|
||||
assert {$res == 1}
|
||||
r debug set-active-expire 1
|
||||
assert_equal [lsort [r hrandfield myhash 5]] "f3 f4 f5"
|
||||
r hpexpire myhash 1 FIELDS 3 f3 f4 f5
|
||||
after 5
|
||||
assert_equal [lsort [r hrandfield myhash 5]] ""
|
||||
assert_equal [r keys *] ""
|
||||
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
r hpexpire myhash 1 FIELDS 1 f1
|
||||
after 5
|
||||
set res [r hrandfield myhash]
|
||||
assert {$res == "f2" || $res == "f3"}
|
||||
r hpexpire myhash 1 FIELDS 1 f2
|
||||
after 5
|
||||
assert_equal [lsort [r hrandfield myhash 5]] "f3"
|
||||
r hpexpire myhash 1 FIELDS 1 f3
|
||||
after 5
|
||||
assert_equal [r hrandfield myhash] ""
|
||||
assert_equal [r keys *] ""
|
||||
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
test "Lazy Expire - HLEN does count expired fields ($type)" {
|
||||
@@ -1054,7 +1012,13 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r hpexpireat h1 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2
|
||||
r hpexpire h1 100000 NX FIELDS 3 f3 f4 f5
|
||||
r hexpire h1 100000 FIELDS 1 f6
|
||||
r hset h5 f1 v1
|
||||
|
||||
# Verify HRANDFIELD deletes expired fields and propagates it
|
||||
r hset h2 f1 v1 f2 v2
|
||||
r hpexpire h2 1 FIELDS 1 f1
|
||||
r hpexpire h2 50 FIELDS 1 f2
|
||||
assert_equal [r hrandfield h4 2] ""
|
||||
after 200
|
||||
|
||||
assert_aof_content $aof {
|
||||
{select *}
|
||||
@@ -1063,7 +1027,11 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
{hpexpireat h1 * FIELDS 1 f2}
|
||||
{hpexpireat h1 * NX FIELDS 3 f3 f4 f5}
|
||||
{hpexpireat h1 * FIELDS 1 f6}
|
||||
{hset h5 f1 v1}
|
||||
{hset h2 f1 v1 f2 v2}
|
||||
{hpexpireat h2 * FIELDS 1 f1}
|
||||
{hpexpireat h2 * FIELDS 1 f2}
|
||||
{hdel h2 f1}
|
||||
{hdel h2 f2}
|
||||
}
|
||||
|
||||
array set keyAndFields1 [dumpAllHashes r]
|
||||
@@ -1086,6 +1054,7 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r flushall ; # Clean up keyspace to avoid interference by keys from other tests
|
||||
set repl [attach_to_replication_stream]
|
||||
|
||||
# HEXPIRE/HPEXPIRE should be translated into HPEXPIREAT
|
||||
r hset h1 f1 v1
|
||||
r hexpireat h1 [expr [clock seconds]+100] NX FIELDS 1 f1
|
||||
r hset h2 f2 v2
|
||||
@@ -1109,6 +1078,28 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
close_replication_stream $repl
|
||||
} {} {needs:repl}
|
||||
|
||||
test {HRANDFIELD delete expired fields and propagate DELs to replica} {
|
||||
r flushall
|
||||
set repl [attach_to_replication_stream]
|
||||
|
||||
r hset h4 f1 v1 f2 v2
|
||||
r hpexpire h4 1 FIELDS 1 f1
|
||||
r hpexpire h4 2 FIELDS 1 f2
|
||||
after 100
|
||||
assert_equal [r hrandfield h4 2] ""
|
||||
|
||||
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{hset h4 f1 v1 f2 v2}
|
||||
{hpexpireat h4 * FIELDS 1 f1}
|
||||
{hpexpireat h4 * FIELDS 1 f2}
|
||||
{hdel h4 f1}
|
||||
{hdel h4 f2}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
} {} {needs:repl}
|
||||
|
||||
# Start another server to test replication of TTLs
|
||||
start_server {tags {needs:repl external:skip}} {
|
||||
# Set the outer layer server as primary
|
||||
|
||||
Reference in New Issue
Block a user