mirror of
https://github.com/valkey-io/valkey.git
synced 2026-10-28 08:24:42 +08:00
Copy expired-but-unreaped fields in hashTypeDup (#4939)
For a hashtable encoded hash, hashTypeDup() iterates with validation on, so COPY drops fields whose TTL has passed but that active expiry has not reaped yet. A replica applies the same COPY with field expiry ignored and keeps them. The primary's copy never had them, so it never reaps them or propagates their deletion, and the replica keeps them for as long as the link stays up. Ignore TTL while copying, the same way #4704 does for listpack conversion. The listpack branch already copies everything. The reaper then deletes the expired fields from the copy and propagates the deletion. --------- Signed-off-by: Aman Kumar <amankumar020003@gmail.com>
This commit is contained in:
1 parent
9fdda3152e
commit
fbb5ab63bb
2 files changed
+45
No files matched your search
@@ -1113,6 +1113,12 @@ robj *hashTypeDup(robj *o) {
|
||||
hobj = createObject(OBJ_HASH, ht);
|
||||
hobj->encoding = OBJ_ENCODING_HASHTABLE;
|
||||
|
||||
/* Copy logically-expired fields too, like the listpack branch does. A
|
||||
* replica applying the same COPY ignores field expiry and keeps them,
|
||||
* so dropping them here means their deletion is never propagated. The
|
||||
* reaper deletes them from the copy and propagates it. */
|
||||
bool prev_state = false;
|
||||
hashTypeIgnoreTTL(o, true, &prev_state);
|
||||
hashTypeInitIterator(o, &hi);
|
||||
while (hashTypeNext(&hi) != C_ERR) {
|
||||
/* Extract a field-value pair from an original hash object.*/
|
||||
@@ -1128,6 +1134,7 @@ robj *hashTypeDup(robj *o) {
|
||||
hashTypeTrackEntry(hobj, entry);
|
||||
}
|
||||
hashTypeResetIterator(&hi);
|
||||
hashTypeIgnoreTTL(o, prev_state, NULL);
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
|
||||
@@ -5398,6 +5398,44 @@ start_server {tags {"hashexpire external:skip"}} {
|
||||
assert_equal {} [$replica HGET myhash f1]
|
||||
assert_equal {f2 f3 f4} [lsort [$replica HKEYS myhash]]
|
||||
} {} {needs:debug}
|
||||
|
||||
# The value size picks the encoding on both sides.
|
||||
foreach {encoding f2_value} [list listpack v2 hashtable [string repeat x 128]] {
|
||||
test "COPY of a hash holding an expired-but-unreaped field keeps the replica in sync - $encoding" {
|
||||
lassign [setup_replication_test $primary $replica $primary_host $primary_port] primary_initial_expired replica_initial_expired
|
||||
|
||||
$primary DEBUG SET-ACTIVE-EXPIRE 0
|
||||
$replica DEBUG SET-ACTIVE-EXPIRE 0
|
||||
|
||||
$primary HSET myhash f1 v1 f2 $f2_value
|
||||
$primary HPEXPIRE myhash 1 FIELDS 1 f1
|
||||
wait_for_condition 100 10 {
|
||||
[$primary HEXISTS myhash f1] == 0
|
||||
} else {
|
||||
fail "Field f1 was never logically expired on the primary"
|
||||
}
|
||||
assert_equal $encoding [$primary object encoding myhash]
|
||||
|
||||
# The replica ignores field expiry while applying COPY, so its
|
||||
# copy has f1. The primary has to copy f1 too, otherwise it
|
||||
# never propagates the deletion of f1 from the copy.
|
||||
$primary COPY myhash mycopy
|
||||
wait_for_ofs_sync $primary $replica
|
||||
assert_equal $encoding [$primary object encoding mycopy]
|
||||
assert_equal $encoding [$replica object encoding mycopy]
|
||||
assert_equal 2 [$primary HLEN mycopy]
|
||||
assert_equal [$primary HLEN mycopy] [$replica HLEN mycopy]
|
||||
# The copy must be tracked, otherwise active expiry never reaps f1 from it.
|
||||
assert_equal 2 [get_keys_with_volatile_items $primary]
|
||||
|
||||
$primary DEBUG SET-ACTIVE-EXPIRE 1
|
||||
$replica DEBUG SET-ACTIVE-EXPIRE 1
|
||||
wait_for_active_expiry $primary mycopy 1 $primary_initial_expired 2
|
||||
wait_for_ofs_sync $primary $replica
|
||||
assert_equal {f2} [$replica HKEYS mycopy]
|
||||
assert_equal {f2} [$replica HKEYS myhash]
|
||||
} {} {needs:debug}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user