Skip to content

Commit db4fc2a

Browse files
authored
Fix HINCRBYFLOAT removes field expiration on replica (redis#14224)
Fixes redis#14218 Before, we replicate HINCRBYFLOAT as an HSET command with the final value in order to make sure that differences in float precision or formatting will not create differences in replicas or after an AOF restart. However, on the replica side, if the field has an expiration time, HSET will remove it, even though the master retains it. This leads to inconsistencies between the master and the replica. To address this, we now use the HSETEX command with the KEEPTTL flag instead of HSET, ensuring that the field’s TTL is preserved. This bug was introduced in version 7.4, but the HSETEX command was only implemented from version 8.0. Therefore, this patch does not fix the issue in the 7.4 branch, a separate commit is needed to address it in 7.4.
1 parent e9d2bf4 commit db4fc2a

File tree

4 files changed

+55
-6
lines changed

4 files changed

+55
-6
lines changed

src/server.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,6 +2127,7 @@ void createSharedObjects(void) {
21272127
shared.hpexpireat = createStringObject("HPEXPIREAT",10);
21282128
shared.hpersist = createStringObject("HPERSIST",8);
21292129
shared.hdel = createStringObject("HDEL",4);
2130+
shared.hsetex = createStringObject("HSETEX",6);
21302131

21312132
/* Shared command argument */
21322133
shared.left = createStringObject("left",4);
@@ -2149,6 +2150,7 @@ void createSharedObjects(void) {
21492150
shared.special_asterick = createStringObject("*",1);
21502151
shared.special_equals = createStringObject("=",1);
21512152
shared.redacted = makeObjectShared(createStringObject("(redacted)",10));
2153+
shared.fields = createStringObject("FIELDS",6);
21522154

21532155
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
21542156
shared.integers[j] =

src/server.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,9 +1526,9 @@ struct sharedObjectsStruct {
15261526
*rpop, *lpop, *lpush, *rpoplpush, *lmove, *blmove, *zpopmin, *zpopmax,
15271527
*emptyscan, *multi, *exec, *left, *right, *hset, *srem, *xgroup, *xclaim,
15281528
*script, *replconf, *eval, *persist, *set, *pexpireat, *pexpire,
1529-
*hdel, *hpexpireat, *hpersist,
1529+
*hdel, *hpexpireat, *hpersist, *hsetex,
15301530
*time, *pxat, *absttl, *retrycount, *force, *justid, *entriesread,
1531-
*lastid, *ping, *setid, *keepttl, *load, *createconsumer,
1531+
*lastid, *ping, *setid, *keepttl, *load, *createconsumer, *fields,
15321532
*getack, *special_asterick, *special_equals, *default_username, *redacted,
15331533
*ssubscribebulk,*sunsubscribebulk, *smessagebulk,
15341534
*select[PROTO_SHARED_SELECT_CMDS],

src/t_hash.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2562,13 +2562,14 @@ void hincrbyfloatCommand(client *c) {
25622562
notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
25632563
server.dirty++;
25642564

2565-
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
2565+
/* Always replicate HINCRBYFLOAT as an HSETEX command with the final value
25662566
* in order to make sure that differences in float precision or formatting
2567-
* will not create differences in replicas or after an AOF restart. */
2567+
* will not create differences in replicas or after an AOF restart.
2568+
* The KEEPTTL flag is used to make sure the field TTL is preserved. */
25682569
robj *newobj;
25692570
newobj = createRawStringObject(buf,len);
2570-
rewriteClientCommandArgument(c,0,shared.hset);
2571-
rewriteClientCommandArgument(c,3,newobj);
2571+
rewriteClientCommandVector(c, 7, shared.hsetex, c->argv[1], shared.keepttl,
2572+
shared.fields, shared.integers[1], c->argv[2], newobj);
25722573
decrRefCount(newobj);
25732574
}
25742575

tests/unit/type/hash-field-expire.tcl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,5 +1956,51 @@ start_server {tags {"external:skip needs:debug"}} {
19561956
}
19571957
close_replication_stream $repl
19581958
} {} {needs:repl}
1959+
1960+
test "HINCRBYFLOAT command won't remove field expiration on replica ($type)" {
1961+
r flushall
1962+
set repl [attach_to_replication_stream]
1963+
1964+
r hsetex h1 EX 100 FIELDS 1 f1 1
1965+
r hset h1 f2 1
1966+
r hincrbyfloat h1 f1 1.1
1967+
r hincrbyfloat h1 f2 1.1
1968+
1969+
# HINCRBYFLOAT will be replicated as HSETEX with KEEPTTL flag
1970+
assert_replication_stream $repl {
1971+
{select *}
1972+
{hsetex h1 PXAT * FIELDS 1 f1 1}
1973+
{hset h1 f2 1}
1974+
{hsetex h1 KEEPTTL FIELDS 1 f1 *}
1975+
{hsetex h1 KEEPTTL FIELDS 1 f2 *}
1976+
}
1977+
close_replication_stream $repl
1978+
1979+
start_server {tags {external:skip}} {
1980+
r -1 flushall
1981+
r slaveof [srv -1 host] [srv -1 port]
1982+
wait_for_sync r
1983+
1984+
r -1 hsetex h1 EX 100 FIELDS 1 f1 1
1985+
r -1 hset h1 f2 1
1986+
wait_for_ofs_sync [srv -1 client] [srv 0 client]
1987+
assert_range [r httl h1 FIELDS 1 f1] 90 100
1988+
assert_equal {-1} [r httl h1 FIELDS 1 f2]
1989+
1990+
r -1 hincrbyfloat h1 f1 1.1
1991+
r -1 hincrbyfloat h1 f2 1.1
1992+
1993+
# Expiration time should not be removed on replica and the value
1994+
# should be equal to the master.
1995+
wait_for_ofs_sync [srv -1 client] [srv 0 client]
1996+
assert_range [r httl h1 FIELDS 1 f1] 90 100
1997+
assert_equal [r -1 hget h1 f1] [r hget h1 f1]
1998+
1999+
# The field f2 should not have any expiration on replica either even
2000+
# though it was set using HSET with KEEPTTL flag.
2001+
assert_equal {-1} [r httl h1 FIELDS 1 f2]
2002+
assert_equal [r -1 hget h1 f2] [r hget h1 f2]
2003+
}
2004+
} {} {needs:repl external:skip}
19592005
}
19602006
}

0 commit comments

Comments
 (0)