Unverified Commit 0637b4ea authored by YaacovHazan's avatar YaacovHazan Committed by GitHub
Browse files

Redis 7.4 RC2 (#13371)

Upgrade urgency LOW: This is the second Release Candidate for Redis 7.4.

Performance and resource utilization improvements
=================================================
* #13296 Optimize CPU cache efficiency

Changes to new 7.4 new features (compared to 7.4 RC1)
=====================================================
* #13343 Hash - expiration of individual fields: when key does not exist
- reply with an array (nonexisting code for each field)
* #13329 Hash - expiration of individual fields: new keyspace event:
`hexpired`

Modules API - Potentially breaking changes to new 7.4 features (compared
to 7.4 RC1)

====================================================================================
* #13326 Hash - expiration of individual fields: avoid lazy expire when
called from a Modules API function
parents 4606f91d 4000bb2e
...@@ -175,6 +175,7 @@ void rdbCheckSetupSignals(void) { ...@@ -175,6 +175,7 @@ void rdbCheckSetupSignals(void) {
* otherwise the already open file 'fp' is checked. */ * otherwise the already open file 'fp' is checked. */
int redis_check_rdb(char *rdbfilename, FILE *fp) { int redis_check_rdb(char *rdbfilename, FILE *fp) {
uint64_t dbid; uint64_t dbid;
int selected_dbid = -1;
int type, rdbver; int type, rdbver;
char buf[1024]; char buf[1024];
long long expiretime, now = mstime(); long long expiretime, now = mstime();
...@@ -246,6 +247,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -246,6 +247,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr; goto eoferr;
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid); rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
selected_dbid = dbid;
continue; /* Read type again. */ continue; /* Read type again. */
} else if (type == RDB_OPCODE_RESIZEDB) { } else if (type == RDB_OPCODE_RESIZEDB) {
/* RESIZEDB: Hint about the size of the keys in the currently /* RESIZEDB: Hint about the size of the keys in the currently
...@@ -331,7 +333,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -331,7 +333,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
rdbstate.keys++; rdbstate.keys++;
/* Read value */ /* Read value */
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE; rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
if ((val = rdbLoadObject(type,&rdb,key->ptr,NULL,NULL,NULL)) == NULL) if ((val = rdbLoadObject(type,&rdb,key->ptr,selected_dbid,NULL)) == NULL)
goto eoferr; goto eoferr;
/* Check if the key already expired. */ /* Check if the key already expired. */
if (expiretime != -1 && expiretime < now) if (expiretime != -1 && expiretime < now)
......
...@@ -189,6 +189,9 @@ int canFeedReplicaReplBuffer(client *replica) { ...@@ -189,6 +189,9 @@ int canFeedReplicaReplBuffer(client *replica) {
/* Don't feed replicas that are still waiting for BGSAVE to start. */ /* Don't feed replicas that are still waiting for BGSAVE to start. */
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) return 0; if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) return 0;
/* Don't feed replicas that are going to be closed ASAP. */
if (replica->flags & CLIENT_CLOSE_ASAP) return 0;
return 1; return 1;
} }
......
...@@ -74,6 +74,7 @@ extern scriptFlag scripts_flags_def[]; ...@@ -74,6 +74,7 @@ extern scriptFlag scripts_flags_def[];
void luaEnvInit(void); void luaEnvInit(void);
lua_State *createLuaState(void); lua_State *createLuaState(void);
dict *getLuaScripts(void);
uint64_t scriptFlagsToCmdFlags(uint64_t cmd_flags, uint64_t script_flags); uint64_t scriptFlagsToCmdFlags(uint64_t cmd_flags, uint64_t script_flags);
int scriptPrepareForRun(scriptRunCtx *r_ctx, client *engine_client, client *caller, const char *funcname, uint64_t script_flags, int ro); int scriptPrepareForRun(scriptRunCtx *r_ctx, client *engine_client, client *caller, const char *funcname, uint64_t script_flags, int ro);
void scriptResetRun(scriptRunCtx *r_ctx); void scriptResetRun(scriptRunCtx *r_ctx);
......
...@@ -334,6 +334,10 @@ uint64_t dictObjHash(const void *key) { ...@@ -334,6 +334,10 @@ uint64_t dictObjHash(const void *key) {
return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr));
} }
uint64_t dictPtrHash(const void *key) {
return dictGenHashFunction((unsigned char*)&key,sizeof(key));
}
uint64_t dictSdsHash(const void *key) { uint64_t dictSdsHash(const void *key) {
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
} }
......
...@@ -2071,7 +2071,8 @@ struct redisServer { ...@@ -2071,7 +2071,8 @@ struct redisServer {
char *locale_collate; char *locale_collate;
}; };
#define MAX_KEYS_BUFFER 256 /* we use 6 so that all getKeyResult fits a cacheline */
#define MAX_KEYS_BUFFER 6
typedef struct { typedef struct {
int pos; /* The position of the key within the client array */ int pos; /* The position of the key within the client array */
...@@ -2084,12 +2085,12 @@ typedef struct { ...@@ -2084,12 +2085,12 @@ typedef struct {
* for returning channel information. * for returning channel information.
*/ */
typedef struct { typedef struct {
keyReference keysbuf[MAX_KEYS_BUFFER]; /* Pre-allocated buffer, to save heap allocations */
keyReference *keys; /* Key indices array, points to keysbuf or heap */
int numkeys; /* Number of key indices return */ int numkeys; /* Number of key indices return */
int size; /* Available array size */ int size; /* Available array size */
keyReference keysbuf[MAX_KEYS_BUFFER]; /* Pre-allocated buffer, to save heap allocations */
keyReference *keys; /* Key indices array, points to keysbuf or heap */
} getKeysResult; } getKeysResult;
#define GETKEYS_RESULT_INIT { {{0}}, NULL, 0, MAX_KEYS_BUFFER } #define GETKEYS_RESULT_INIT { 0, MAX_KEYS_BUFFER, {{0}}, NULL }
/* Key specs definitions. /* Key specs definitions.
* *
...@@ -3163,7 +3164,9 @@ robj *setTypeDup(robj *o); ...@@ -3163,7 +3164,9 @@ robj *setTypeDup(robj *o);
typedef struct listpackEx { typedef struct listpackEx {
ExpireMeta meta; /* To be used in order to register the hash in the ExpireMeta meta; /* To be used in order to register the hash in the
global ebuckets (i.e. db->hexpires) with next, global ebuckets (i.e. db->hexpires) with next,
minimum, hash-field to expire. */ minimum, hash-field to expire. TTL value might be
inaccurate up-to few seconds due to optimization
consideration. */
sds key; /* reference to the key, same one that stored in sds key; /* reference to the key, same one that stored in
db->dict. Will be used from active-expiration flow db->dict. Will be used from active-expiration flow
for notification and deletion of the object, if for notification and deletion of the object, if
...@@ -3178,7 +3181,9 @@ typedef struct dictExpireMetadata { ...@@ -3178,7 +3181,9 @@ typedef struct dictExpireMetadata {
ExpireMeta expireMeta; /* embedded ExpireMeta in dict. ExpireMeta expireMeta; /* embedded ExpireMeta in dict.
To be used in order to register the hash in the To be used in order to register the hash in the
global ebuckets (i.e db->hexpires) with next, global ebuckets (i.e db->hexpires) with next,
minimum, hash-field to expire */ minimum, hash-field to expire. TTL value might be
inaccurate up-to few seconds due to optimization
consideration. */
ebuckets hfe; /* DS of Hash Fields Expiration, associated to each hash */ ebuckets hfe; /* DS of Hash Fields Expiration, associated to each hash */
sds key; /* reference to the key, same one that stored in sds key; /* reference to the key, same one that stored in
db->dict. Will be used from active-expiration flow db->dict. Will be used from active-expiration flow
...@@ -3191,9 +3196,16 @@ typedef struct dictExpireMetadata { ...@@ -3191,9 +3196,16 @@ typedef struct dictExpireMetadata {
#define HASH_SET_TAKE_VALUE (1<<1) #define HASH_SET_TAKE_VALUE (1<<1)
#define HASH_SET_COPY 0 #define HASH_SET_COPY 0
/* Hash field lazy expiration flags. Used by core hashTypeGetValue() and its callers */
#define HFE_LAZY_EXPIRE (0) /* Delete expired field, and if last field also the hash */
#define HFE_LAZY_AVOID_FIELD_DEL (1<<0) /* Avoid deleting expired field */
#define HFE_LAZY_AVOID_HASH_DEL (1<<1) /* Avoid deleting hash if the field is the last one */
#define HFE_LAZY_NO_NOTIFICATION (1<<2) /* Do not send notification, used when multiple fields
* may expire and only one notification is desired. */
void hashTypeConvert(robj *o, int enc, ebuckets *hexpires); void hashTypeConvert(robj *o, int enc, ebuckets *hexpires);
void hashTypeTryConversion(redisDb *db, robj *subject, robj **argv, int start, int end); void hashTypeTryConversion(redisDb *db, robj *subject, robj **argv, int start, int end);
int hashTypeExists(redisDb *db, robj *o, sds key, int *isHashDeleted); int hashTypeExists(redisDb *db, robj *o, sds key, int hfeFlags, int *isHashDeleted);
int hashTypeDelete(robj *o, void *key, int isSdsField); int hashTypeDelete(robj *o, void *key, int isSdsField);
unsigned long hashTypeLength(const robj *o, int subtractExpiredFields); unsigned long hashTypeLength(const robj *o, int subtractExpiredFields);
hashTypeIterator *hashTypeInitIterator(robj *subject); hashTypeIterator *hashTypeInitIterator(robj *subject);
...@@ -3210,20 +3222,17 @@ void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, ...@@ -3210,20 +3222,17 @@ void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr,
unsigned int *vlen, long long *vll, uint64_t *expireTime); unsigned int *vlen, long long *vll, uint64_t *expireTime);
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what); sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what);
hfield hashTypeCurrentObjectNewHfield(hashTypeIterator *hi); hfield hashTypeCurrentObjectNewHfield(hashTypeIterator *hi);
robj *hashTypeGetValueObject(redisDb *db, robj *o, sds field, int *isHashDeleted); robj *hashTypeGetValueObject(redisDb *db, robj *o, sds field, int hfeFlags, int *isHashDeleted);
int hashTypeSet(redisDb *db, robj *o, sds field, sds value, int flags); int hashTypeSet(redisDb *db, robj *o, sds field, sds value, int flags);
robj *hashTypeDup(robj *o, sds newkey, uint64_t *minHashExpire); robj *hashTypeDup(robj *o, sds newkey, uint64_t *minHashExpire);
uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o); uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o);
void hashTypeAddToExpires(redisDb *db, sds key, robj *hashObj, uint64_t expireTime); void hashTypeAddToExpires(redisDb *db, sds key, robj *hashObj, uint64_t expireTime);
void hashTypeFree(robj *o); void hashTypeFree(robj *o);
int hashTypeIsExpired(const robj *o, uint64_t expireAt); int hashTypeIsExpired(const robj *o, uint64_t expireAt);
uint64_t hashTypeGetMinExpire(robj *o);
unsigned char *hashTypeListpackGetLp(robj *o); unsigned char *hashTypeListpackGetLp(robj *o);
uint64_t hashTypeGetMinExpire(robj *o); uint64_t hashTypeGetMinExpire(robj *o, int accurate);
void hashTypeUpdateKeyRef(robj *o, sds newkey); void hashTypeUpdateKeyRef(robj *o, sds newkey);
ebuckets *hashTypeGetDictMetaHFE(dict *d); ebuckets *hashTypeGetDictMetaHFE(dict *d);
uint64_t hashTypeGetMinExpire(robj *keyObj);
uint64_t hashTypeGetNextTimeToExpire(robj *o);
void initDictExpireMetadata(sds key, robj *o); void initDictExpireMetadata(sds key, robj *o);
struct listpackEx *listpackExCreate(void); struct listpackEx *listpackExCreate(void);
void listpackExAddNew(robj *o, char *field, size_t flen, void listpackExAddNew(robj *o, char *field, size_t flen,
...@@ -3531,6 +3540,7 @@ void startEvictionTimeProc(void); ...@@ -3531,6 +3540,7 @@ void startEvictionTimeProc(void);
/* Keys hashing / comparison functions for dict.c hash tables. */ /* Keys hashing / comparison functions for dict.c hash tables. */
uint64_t dictSdsHash(const void *key); uint64_t dictSdsHash(const void *key);
uint64_t dictPtrHash(const void *key);
uint64_t dictSdsCaseHash(const void *key); uint64_t dictSdsCaseHash(const void *key);
int dictSdsKeyCompare(dict *d, const void *key1, const void *key2); int dictSdsKeyCompare(dict *d, const void *key1, const void *key2);
int dictSdsMstrKeyCompare(dict *d, const void *sdsLookup, const void *mstrStored); int dictSdsMstrKeyCompare(dict *d, const void *sdsLookup, const void *mstrStored);
......
...@@ -95,7 +95,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { ...@@ -95,7 +95,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
/* Retrieve value from hash by the field name. The returned object /* Retrieve value from hash by the field name. The returned object
* is a new object with refcount already incremented. */ * is a new object with refcount already incremented. */
int isHashDeleted; int isHashDeleted;
o = hashTypeGetValueObject(db, o, fieldobj->ptr, &isHashDeleted); o = hashTypeGetValueObject(db, o, fieldobj->ptr, HFE_LAZY_EXPIRE, &isHashDeleted);
if (isHashDeleted) if (isHashDeleted)
goto noobj; goto noobj;
......
This diff is collapsed.
#define REDIS_VERSION "7.3.240" #define REDIS_VERSION "7.3.241"
#define REDIS_VERSION_NUM 0x000703f0 #define REDIS_VERSION_NUM 0x000703f1
...@@ -429,8 +429,8 @@ start_server [list overrides [list "dir" $server_path]] { ...@@ -429,8 +429,8 @@ start_server [list overrides [list "dir" $server_path]] {
r HMSET key a 1 b 2 c 3 d 4 e 5 r HMSET key a 1 b 2 c 3 d 4 e 5
# expected to be expired long after restart # expected to be expired long after restart
r HEXPIREAT key 2524600800 FIELDS 1 a r HEXPIREAT key 2524600800 FIELDS 1 a
# expected long TTL value (6 bytes) is saved and loaded correctly # expected long TTL value (46 bits) is saved and loaded correctly
r HPEXPIREAT key 188900976391764 FIELDS 1 b r HPEXPIREAT key 65755674080852 FIELDS 1 b
# expected to be already expired after restart # expected to be already expired after restart
r HPEXPIRE key 80 FIELDS 1 d r HPEXPIRE key 80 FIELDS 1 d
# expected to be expired soon after restart # expected to be expired soon after restart
...@@ -443,7 +443,7 @@ start_server [list overrides [list "dir" $server_path]] { ...@@ -443,7 +443,7 @@ start_server [list overrides [list "dir" $server_path]] {
wait_done_loading r wait_done_loading r
assert_equal [lsort [r hgetall key]] "1 2 3 a b c" assert_equal [lsort [r hgetall key]] "1 2 3 a b c"
assert_equal [r hpexpiretime key FIELDS 3 a b c] {2524600800000 188900976391764 -1} assert_equal [r hpexpiretime key FIELDS 3 a b c] {2524600800000 65755674080852 -1}
assert_equal [s rdb_last_load_keys_loaded] 1 assert_equal [s rdb_last_load_keys_loaded] 1
# wait until expired_hash_fields equals 2 # wait until expired_hash_fields equals 2
......
...@@ -302,6 +302,21 @@ int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -302,6 +302,21 @@ int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int propagateTestVerbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc < 2){
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
long long replicate_num;
RedisModule_StringToLongLong(argv[1], &replicate_num);
/* Replicate the command verbatim for the specified number of times. */
for (long long i = 0; i < replicate_num; i++)
RedisModule_ReplicateVerbatim(ctx);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
...@@ -369,6 +384,11 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -369,6 +384,11 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"write",1,1,1) == REDISMODULE_ERR) "write",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.verbatim",
propagateTestVerbatim,
"write",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
......
...@@ -21,6 +21,45 @@ start_server {tags {"modules"}} { ...@@ -21,6 +21,45 @@ start_server {tags {"modules"}} {
r hgetall k r hgetall k
} {squirrel ofcourse banana no what nothing something nice} } {squirrel ofcourse banana no what nothing something nice}
test {Module hash - set (override) NX expired field successfully} {
r debug set-active-expire 0
r del H1 H2
r hash.set H1 "n" f1 v1
r hpexpire H1 1 FIELDS 1 f1
r hash.set H2 "n" f1 v1 f2 v2
r hpexpire H2 1 FIELDS 1 f1
after 5
assert_equal 0 [r hash.set H1 "n" f1 xx]
assert_equal "f1 xx" [r hgetall H1]
assert_equal 0 [r hash.set H2 "n" f1 yy]
assert_equal "f1 f2 v2 yy" [lsort [r hgetall H2]]
r debug set-active-expire 1
} {OK} {needs:debug}
test {Module hash - set XX of expired field gets failed as expected} {
r debug set-active-expire 0
r del H1 H2
r hash.set H1 "n" f1 v1
r hpexpire H1 1 FIELDS 1 f1
r hash.set H2 "n" f1 v1 f2 v2
r hpexpire H2 1 FIELDS 1 f1
after 5
# expected to fail on condition XX. hgetall should return empty list
r hash.set H1 "x" f1 xx
assert_equal "" [lsort [r hgetall H1]]
# But expired field was not lazy deleted
assert_equal 1 [r hlen H1]
# expected to fail on condition XX. hgetall should return list without expired f1
r hash.set H2 "x" f1 yy
assert_equal "f2 v2" [lsort [r hgetall H2]]
# But expired field was not lazy deleted
assert_equal 2 [r hlen H2]
r debug set-active-expire 1
} {OK} {needs:debug}
test "Unload the module - hash" { test "Unload the module - hash" {
assert_equal {OK} [r module unload hash] assert_equal {OK} [r module unload hash]
} }
......
...@@ -761,3 +761,41 @@ tags "modules aof" { ...@@ -761,3 +761,41 @@ tags "modules aof" {
} }
} }
} }
# This test does not really test module functionality, but rather uses a module
# command to test Redis replication mechanisms.
test {Replicas that was marked as CLIENT_CLOSE_ASAP should not keep the replication backlog from been trimmed} {
start_server [list overrides [list loadmodule "$testmodule"]] {
set replica [srv 0 client]
start_server [list overrides [list loadmodule "$testmodule"]] {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
$master config set client-output-buffer-limit "replica 10mb 5mb 0"
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_sync $replica
test {module propagates from timer} {
# Replicate large commands to make the replica disconnected.
$master write [format_command propagate-test.verbatim 100000 [string repeat "a" 1000]] ;# almost 100mb
# Execute this command together with module commands within the same
# event loop to prevent periodic cleanup of replication backlog.
$master write [format_command info memory]
$master flush
$master read ;# propagate-test.verbatim
set res [$master read] ;# info memory
# Wait for the replica to be disconnected.
wait_for_log_messages 0 {"*flags=S*scheduled to be closed ASAP for overcoming of output buffer limits*"} 0 1500 10
# Due to the replica reaching the soft limit (5MB), memory peaks should not significantly
# exceed the replica soft limit. Furthermore, as the replica release its reference to
# replication backlog, it should be properly trimmed, the memory usage of replication
# backlog should not significantly exceed repl-backlog-size (default 1MB). */
assert_lessthan [getInfoProperty $res used_memory_peak] 10000000;# less than 10mb
assert_lessthan [getInfoProperty $res mem_replication_backlog] 2000000;# less than 2mb
}
}
}
}
...@@ -25,12 +25,16 @@ start_server {tags {"modules"}} { ...@@ -25,12 +25,16 @@ start_server {tags {"modules"}} {
} {{f1 1}} } {{f1 1}}
test {Module scan hash listpack with hexpire} { test {Module scan hash listpack with hexpire} {
r hmset hh f1 v1 f2 v2 r debug set-active-expire 0
r hmset hh f1 v1 f2 v2 f3 v3
r hexpire hh 100000 fields 1 f1 r hexpire hh 100000 fields 1 f1
r hpexpire hh 1 fields 1 f3
after 10
assert_range [r httl hh fields 1 f1] 10000 100000 assert_range [r httl hh fields 1 f1] 10000 100000
assert_encoding listpackex hh assert_encoding listpackex hh
r debug set-active-expire 1
lsort [r scan.scan_key hh] lsort [r scan.scan_key hh]
} {{f1 v1} {f2 v2}} } {{f1 v1} {f2 v2}} {needs:debug}
test {Module scan hash dict} { test {Module scan hash dict} {
r config set hash-max-ziplist-entries 2 r config set hash-max-ziplist-entries 2
...@@ -44,10 +48,22 @@ start_server {tags {"modules"}} { ...@@ -44,10 +48,22 @@ start_server {tags {"modules"}} {
r del hh r del hh
r hmset hh f1 v1 f2 v2 f3 v3 r hmset hh f1 v1 f2 v2 f3 v3
r hexpire hh 100000 fields 1 f2 r hexpire hh 100000 fields 1 f2
r hpexpire hh 5 fields 1 f3
assert_range [r httl hh fields 1 f2] 10000 100000 assert_range [r httl hh fields 1 f2] 10000 100000
assert_encoding hashtable hh assert_encoding hashtable hh
after 10
lsort [r scan.scan_key hh] lsort [r scan.scan_key hh]
} {{f1 v1} {f2 v2} {f3 v3}} } {{f1 v1} {f2 v2}}
test {Module scan hash with hexpire can return no items} {
r del hh
r debug set-active-expire 0
r hmset hh f1 v1 f2 v2 f3 v3
r hpexpire hh 1 fields 3 f1 f2 f3
after 10
r debug set-active-expire 1
lsort [r scan.scan_key hh]
} {} {needs:debug}
test {Module scan zset listpack} { test {Module scan zset listpack} {
r zadd zz 1 f1 2 f2 r zadd zz 1 f1 2 f2
......
...@@ -356,16 +356,17 @@ start_server {tags {"pubsub network"}} { ...@@ -356,16 +356,17 @@ start_server {tags {"pubsub network"}} {
foreach {type max_lp_entries} {listpackex 512 hashtable 0} { foreach {type max_lp_entries} {listpackex 512 hashtable 0} {
test "Keyspace notifications: hash events test ($type)" { test "Keyspace notifications: hash events test ($type)" {
r config set hash-max-listpack-entries $max_lp_entries r config set hash-max-listpack-entries $max_lp_entries
r config set notify-keyspace-events Kh r config set notify-keyspace-events Khg
r del myhash r del myhash
set rd1 [redis_deferring_client] set rd1 [redis_deferring_client]
assert_equal {1} [psubscribe $rd1 *] assert_equal {1} [psubscribe $rd1 *]
r hmset myhash yes 1 no 0 r hmset myhash yes 1 no 0 f1 1 f2 2 f3_hdel 3
r hincrby myhash yes 10 r hincrby myhash yes 10
r hexpire myhash 999999 FIELDS 1 yes r hexpire myhash 999999 FIELDS 1 yes
r hexpireat myhash [expr {[clock seconds] + 999999}] NX FIELDS 1 no r hexpireat myhash [expr {[clock seconds] + 999999}] NX FIELDS 1 no
r hpexpire myhash 999999 FIELDS 1 yes r hpexpire myhash 999999 FIELDS 1 yes
r hpersist myhash FIELDS 1 yes r hpersist myhash FIELDS 1 yes
r hpexpire myhash 0 FIELDS 1 yes
assert_encoding $type myhash assert_encoding $type myhash
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
assert_equal "pmessage * __keyspace@${db}__:myhash hincrby" [$rd1 read] assert_equal "pmessage * __keyspace@${db}__:myhash hincrby" [$rd1 read]
...@@ -373,8 +374,38 @@ start_server {tags {"pubsub network"}} { ...@@ -373,8 +374,38 @@ start_server {tags {"pubsub network"}} {
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
assert_equal "pmessage * __keyspace@${db}__:myhash hpersist" [$rd1 read] assert_equal "pmessage * __keyspace@${db}__:myhash hpersist" [$rd1 read]
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
# Test that we will get `hexpired` notification when
# a hash field is removed by active expire.
r hpexpire myhash 10 FIELDS 1 no
after 100 ;# Wait for active expire
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
# Test that when a field with TTL is deleted by commands like hdel without
# updating the global DS, active expire will not send a notification.
r hpexpire myhash 100 FIELDS 1 f3_hdel
r hdel myhash f3_hdel
after 200 ;# Wait for active expire
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read]
# Test that we will get `hexpired` notification when
# a hash field is removed by lazy expire.
r debug set-active-expire 0
r hpexpire myhash 10 FIELDS 2 f1 f2
after 20
r hmget myhash f1 f2 ;# Trigger lazy expire
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
# We should get only one `hexpired` notification even two fields was expired.
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
# We should get a `del` notification after all fields were expired.
assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read]
r debug set-active-expire 1
$rd1 close $rd1 close
} } {0} {needs:debug}
} ;# foreach } ;# foreach
test "Keyspace notifications: stream events test" { test "Keyspace notifications: stream events test" {
......
...@@ -32,13 +32,6 @@ proc get_hashes_with_expiry_fields {r} { ...@@ -32,13 +32,6 @@ proc get_hashes_with_expiry_fields {r} {
return 0 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} { proc get_keys {l} {
set res {} set res {}
foreach entry $l { foreach entry $l {
...@@ -48,22 +41,6 @@ proc get_keys {l} { ...@@ -48,22 +41,6 @@ proc get_keys {l} {
return $res 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} { proc dumpAllHashes {client} {
set keyAndFields(0,0) 0 set keyAndFields(0,0) 0
unset keyAndFields unset keyAndFields
...@@ -77,36 +54,6 @@ proc dumpAllHashes {client} { ...@@ -77,36 +54,6 @@ proc dumpAllHashes {client} {
return [array get keyAndFields] 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 ######################################### ############################### TESTS #########################################
start_server {tags {"external:skip needs:debug"}} { start_server {tags {"external:skip needs:debug"}} {
...@@ -117,15 +64,12 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -117,15 +64,12 @@ start_server {tags {"external:skip needs:debug"}} {
r config set hash-max-listpack-entries 512 r config set hash-max-listpack-entries 512
} }
test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Returns empty array if key does not exist" { test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Returns array if the key does not exist" {
r del myhash r del myhash
# Make sure we can distinguish between an empty array and a null response assert_equal [r HEXPIRE myhash 1000 FIELDS 1 a] [list $E_NO_FIELD]
r readraw 1 assert_equal [r HEXPIREAT myhash 1000 FIELDS 1 a] [list $E_NO_FIELD]
assert_equal {*0} [r HEXPIRE myhash 1000 FIELDS 1 a] assert_equal [r HPEXPIRE myhash 1000 FIELDS 2 a b] [list $E_NO_FIELD $E_NO_FIELD]
assert_equal {*0} [r HEXPIREAT myhash 1000 FIELDS 1 a] assert_equal [r HPEXPIREAT myhash 1000 FIELDS 2 a b] [list $E_NO_FIELD $E_NO_FIELD]
assert_equal {*0} [r HPEXPIRE myhash 1000 FIELDS 1 a]
assert_equal {*0} [r HPEXPIREAT myhash 1000 FIELDS 1 a]
r readraw 0
} }
test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Verify that the expire time does not overflow" { test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Verify that the expire time does not overflow" {
...@@ -205,15 +149,17 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -205,15 +149,17 @@ start_server {tags {"external:skip needs:debug"}} {
r del myhash r del myhash
r hset myhash f1 v1 r hset myhash f1 v1
assert_error {*Parameter `numFields` should be greater than 0} {r hpexpire myhash 1000 NX FIELDS 0 f1 f2 f3} assert_error {*Parameter `numFields` should be greater than 0} {r hpexpire myhash 1000 NX FIELDS 0 f1 f2 f3}
assert_error {*Parameter `numFields` is more than number of arguments} {r hpexpire myhash 1000 NX FIELDS 4 f1 f2 f3} # <count> not match with actual number of fields
assert_error {*parameter must match the number*} {r hpexpire myhash 1000 NX FIELDS 4 f1 f2 f3}
assert_error {*parameter must match the number*} {r hpexpire myhash 1000 NX FIELDS 2 f1 f2 f3}
} }
test "HPEXPIRE - parameter expire-time near limit of 2^48 ($type)" { test "HPEXPIRE - parameter expire-time near limit of 2^46 ($type)" {
r del myhash r del myhash
r hset myhash f1 v1 r hset myhash f1 v1
# below & above # below & above
assert_equal [r hpexpire myhash [expr (1<<48) - [clock milliseconds] - 1000 ] FIELDS 1 f1] [list $E_OK] assert_equal [r hpexpire myhash [expr (1<<46) - [clock milliseconds] - 1000 ] FIELDS 1 f1] [list $E_OK]
assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<48) - [clock milliseconds] + 100 ] FIELDS 1 f1} assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<46) - [clock milliseconds] + 100 ] FIELDS 1 f1}
} }
test "Lazy Expire - fields are lazy deleted ($type)" { test "Lazy Expire - fields are lazy deleted ($type)" {
...@@ -305,13 +251,10 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -305,13 +251,10 @@ start_server {tags {"external:skip needs:debug"}} {
r flushall async r flushall async
} }
test "HTTL/HPTTL - Returns empty array if key does not exist" { test "HTTL/HPTTL - Returns array if the key does not exist" {
r del myhash r del myhash
# Make sure we can distinguish between an empty array and a null response assert_equal [r HTTL myhash FIELDS 1 a] [list $T_NO_FIELD]
r readraw 1 assert_equal [r HPTTL myhash FIELDS 2 a b] [list $T_NO_FIELD $T_NO_FIELD]
assert_equal {*0} [r HTTL myhash FIELDS 1 a]
assert_equal {*0} [r HPTTL myhash FIELDS 1 a]
r readraw 0
} }
test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" { test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" {
...@@ -320,10 +263,10 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -320,10 +263,10 @@ start_server {tags {"external:skip needs:debug"}} {
r HPEXPIRE myhash 1000 NX FIELDS 1 field1 r HPEXPIRE myhash 1000 NX FIELDS 1 field1
foreach cmd {HTTL HPTTL} { foreach cmd {HTTL HPTTL} {
assert_equal [r $cmd non_exists_key FIELDS 1 f] {}
assert_equal [r $cmd myhash FIELDS 2 field2 non_exists_field] "$T_NO_EXPIRY $T_NO_FIELD" assert_equal [r $cmd myhash FIELDS 2 field2 non_exists_field] "$T_NO_EXPIRY $T_NO_FIELD"
# Set numFields less than actual number of fields. Fine. # <count> not match with actual number of fields
assert_equal [r $cmd myhash FIELDS 1 non_exists_field1 non_exists_field2] "$T_NO_FIELD" assert_error {*parameter must match the number*} {r $cmd myhash FIELDS 1 non_exists_field1 non_exists_field2}
assert_error {*parameter must match the number*} {r $cmd myhash FIELDS 3 non_exists_field1 non_exists_field2}
} }
} }
...@@ -337,13 +280,10 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -337,13 +280,10 @@ start_server {tags {"external:skip needs:debug"}} {
assert_range $ttl 1000 2000 assert_range $ttl 1000 2000
} }
test "HEXPIRETIME/HPEXPIRETIME - Returns empty array if key does not exist" { test "HEXPIRETIME/HPEXPIRETIME - Returns array if the key does not exist" {
r del myhash r del myhash
# Make sure we can distinguish between an empty array and a null response assert_equal [r HEXPIRETIME myhash FIELDS 1 a] [list $T_NO_FIELD]
r readraw 1 assert_equal [r HPEXPIRETIME myhash FIELDS 2 a b] [list $T_NO_FIELD $T_NO_FIELD]
assert_equal {*0} [r HEXPIRETIME myhash FIELDS 1 a]
assert_equal {*0} [r HPEXPIRETIME myhash FIELDS 1 a]
r readraw 0
} }
test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" { test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" {
...@@ -406,22 +346,33 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -406,22 +346,33 @@ start_server {tags {"external:skip needs:debug"}} {
r debug set-active-expire 1 r debug set-active-expire 1
} }
# OPEN: To decide if to delete expired fields at start of HRANDFIELD. test "Test HRANDFIELD deletes all expired fields ($type)" {
# test "Test HRANDFIELD does not return expired fields ($type)" {
# hrandfieldTest 0
# hrandfieldTest 1
# }
test "Test HRANDFIELD can return expired fields ($type)" {
r debug set-active-expire 0 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 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 after 5
set res [cmp_hrandfield_result myhash "f1 f2 f3 f4 f5"] assert_equal [lsort [r hrandfield myhash 5]] "f3 f4 f5"
assert {$res == 1} r hpexpire myhash 1 FIELDS 3 f3 f4 f5
r debug set-active-expire 1 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)" { test "Lazy Expire - HLEN does count expired fields ($type)" {
...@@ -711,12 +662,10 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -711,12 +662,10 @@ start_server {tags {"external:skip needs:debug"}} {
r debug set-active-expire 1 r debug set-active-expire 1
} }
test "HPERSIST - Returns empty array if key does not exist ($type)" { test "HPERSIST - Returns array if the key does not exist ($type)" {
r del myhash r del myhash
# Make sure we can distinguish between an empty array and a null response assert_equal [r HPERSIST myhash FIELDS 1 a] [list $P_NO_FIELD]
r readraw 1 assert_equal [r HPERSIST myhash FIELDS 2 a b] [list $P_NO_FIELD $P_NO_FIELD]
assert_equal {*0} [r HPERSIST myhash FIELDS 1 a]
r readraw 0
} }
test "HPERSIST - input validation ($type)" { test "HPERSIST - input validation ($type)" {
...@@ -726,9 +675,11 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -726,9 +675,11 @@ start_server {tags {"external:skip needs:debug"}} {
r hexpire myhash 1000 NX FIELDS 1 f1 r hexpire myhash 1000 NX FIELDS 1 f1
assert_error {*wrong number of arguments*} {r hpersist myhash} assert_error {*wrong number of arguments*} {r hpersist myhash}
assert_error {*wrong number of arguments*} {r hpersist myhash FIELDS 1} assert_error {*wrong number of arguments*} {r hpersist myhash FIELDS 1}
assert_equal [r hpersist not-exists-key FIELDS 1 f1] {}
assert_equal [r hpersist myhash FIELDS 2 f1 not-exists-field] "$P_OK $P_NO_FIELD" assert_equal [r hpersist myhash FIELDS 2 f1 not-exists-field] "$P_OK $P_NO_FIELD"
assert_equal [r hpersist myhash FIELDS 1 f2] "$P_NO_EXPIRY" assert_equal [r hpersist myhash FIELDS 1 f2] "$P_NO_EXPIRY"
# <count> not match with actual number of fields
assert_error {*parameter must match the number*} {r hpersist myhash FIELDS 2 f1 f2 f3}
assert_error {*parameter must match the number*} {r hpersist myhash FIELDS 4 f1 f2 f3}
} }
test "HPERSIST - verify fields with TTL are persisted ($type)" { test "HPERSIST - verify fields with TTL are persisted ($type)" {
...@@ -969,14 +920,29 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -969,14 +920,29 @@ start_server {tags {"external:skip needs:debug"}} {
r config set hash-max-listpack-entries 512 r config set hash-max-listpack-entries 512
} }
test "Command rewrite and expired hash fields are propagated to replica ($type)" { test "Test Command propagated to replica as expected ($type)" {
start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} { start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} {
set aof [get_last_incr_aof_path r] set aof [get_last_incr_aof_path r]
# Time is in the past so it should propagate HDELs to replica
# and delete the fields
r hset h0 x1 y1 x2 y2
r hexpireat h0 1 fields 3 x1 x2 non_exists_field
r hset h1 f1 v1 f2 v2 r hset h1 f1 v1 f2 v2
# Next command won't be propagated to replica
# because XX condition not met or field not exists
r hexpire h1 10 XX FIELDS 3 f1 f2 non_exists_field
r hpexpire h1 20 FIELDS 1 f1 r hpexpire h1 20 FIELDS 1 f1
r hpexpire h1 30 FIELDS 1 f2
# Next command will be propagate with only field 'f2'
# because NX condition not met for field 'f1'
r hpexpire h1 30 NX FIELDS 2 f1 f2
# Non exists field should be ignored
r hpexpire h1 30 FIELDS 1 non_exists_field r hpexpire h1 30 FIELDS 1 non_exists_field
r hset h2 f1 v1 f2 v2 f3 v3 f4 v4 r hset h2 f1 v1 f2 v2 f3 v3 f4 v4
r hpexpire h2 40 FIELDS 2 f1 non_exists_field r hpexpire h2 40 FIELDS 2 f1 non_exists_field
...@@ -993,11 +959,16 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -993,11 +959,16 @@ start_server {tags {"external:skip needs:debug"}} {
# Assert that each TTL-related command are persisted with absolute timestamps in AOF # Assert that each TTL-related command are persisted with absolute timestamps in AOF
assert_aof_content $aof { assert_aof_content $aof {
{select *} {select *}
{hset h0 x1 y1 x2 y2}
{multi}
{hdel h0 x1}
{hdel h0 x2}
{exec}
{hset h1 f1 v1 f2 v2} {hset h1 f1 v1 f2 v2}
{hpexpireat h1 * FIELDS 1 f1} {hpexpireat h1 * FIELDS 1 f1}
{hpexpireat h1 * FIELDS 1 f2} {hpexpireat h1 * FIELDS 1 f2}
{hset h2 f1 v1 f2 v2 f3 v3 f4 v4} {hset h2 f1 v1 f2 v2 f3 v3 f4 v4}
{hpexpireat h2 * FIELDS 2 f1 non_exists_field} {hpexpireat h2 * FIELDS 1 f1}
{hpexpireat h2 * FIELDS 1 f2} {hpexpireat h2 * FIELDS 1 f2}
{hpexpireat h2 * FIELDS 1 f3} {hpexpireat h2 * FIELDS 1 f3}
{hpexpireat h2 * FIELDS 1 f4} {hpexpireat h2 * FIELDS 1 f4}
...@@ -1067,7 +1038,13 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -1067,7 +1038,13 @@ start_server {tags {"external:skip needs:debug"}} {
r hpexpireat h1 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2 r hpexpireat h1 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2
r hpexpire h1 100000 NX FIELDS 3 f3 f4 f5 r hpexpire h1 100000 NX FIELDS 3 f3 f4 f5
r hexpire h1 100000 FIELDS 1 f6 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 2 f1 f2
after 5
assert_equal [r hrandfield h4 2] ""
after 200
assert_aof_content $aof { assert_aof_content $aof {
{select *} {select *}
...@@ -1076,7 +1053,10 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -1076,7 +1053,10 @@ start_server {tags {"external:skip needs:debug"}} {
{hpexpireat h1 * FIELDS 1 f2} {hpexpireat h1 * FIELDS 1 f2}
{hpexpireat h1 * NX FIELDS 3 f3 f4 f5} {hpexpireat h1 * NX FIELDS 3 f3 f4 f5}
{hpexpireat h1 * FIELDS 1 f6} {hpexpireat h1 * FIELDS 1 f6}
{hset h5 f1 v1} {hset h2 f1 v1 f2 v2}
{hpexpireat h2 * FIELDS 2 f1 f2}
{hdel h2 *}
{hdel h2 *}
} }
array set keyAndFields1 [dumpAllHashes r] array set keyAndFields1 [dumpAllHashes r]
...@@ -1099,12 +1079,16 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -1099,12 +1079,16 @@ start_server {tags {"external:skip needs:debug"}} {
r flushall ; # Clean up keyspace to avoid interference by keys from other tests r flushall ; # Clean up keyspace to avoid interference by keys from other tests
set repl [attach_to_replication_stream] set repl [attach_to_replication_stream]
# HEXPIRE/HPEXPIRE should be translated into HPEXPIREAT
r hset h1 f1 v1 r hset h1 f1 v1
r hexpireat h1 [expr [clock seconds]+100] NX FIELDS 1 f1 r hexpireat h1 [expr [clock seconds]+100] NX FIELDS 1 f1
r hset h2 f2 v2 r hset h2 f2 v2
r hpexpireat h2 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2 r hpexpireat h2 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2
r hset h3 f3 v3 f4 v4 r hset h3 f3 v3 f4 v4 f5 v5
# hpersist does nothing here. Verify it is not propagated.
r hpersist h3 FIELDS 1 f5
r hexpire h3 100 FIELDS 3 f3 f4 non_exists_field r hexpire h3 100 FIELDS 3 f3 f4 non_exists_field
r hpersist h3 FIELDS 1 f3
assert_replication_stream $repl { assert_replication_stream $repl {
{select *} {select *}
...@@ -1112,12 +1096,55 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -1112,12 +1096,55 @@ start_server {tags {"external:skip needs:debug"}} {
{hpexpireat h1 * NX FIELDS 1 f1} {hpexpireat h1 * NX FIELDS 1 f1}
{hset h2 f2 v2} {hset h2 f2 v2}
{hpexpireat h2 * NX FIELDS 1 f2} {hpexpireat h2 * NX FIELDS 1 f2}
{hset h3 f3 v3 f4 v4} {hset h3 f3 v3 f4 v4 f5 v5}
{hpexpireat h3 * FIELDS 3 f3 f4 non_exists_field} {hpexpireat h3 * FIELDS 2 f3 f4}
{hpersist h3 FIELDS 1 f3}
} }
close_replication_stream $repl close_replication_stream $repl
} {} {needs:repl} } {} {needs:repl}
test {HRANDFIELD delete expired fields and propagate DELs to replica} {
r debug set-active-expire 0
r flushall
set repl [attach_to_replication_stream]
# HRANDFIELD delete expired fields and propagate MULTI-EXEC DELs. Reply none.
r hset h1 f1 v1 f2 v2
r hpexpire h1 1 FIELDS 2 f1 f2
after 5
assert_equal [r hrandfield h1 2] ""
# HRANDFIELD delete expired field and propagate DEL. Reply non-expired field.
r hset h2 f1 v1 f2 v2
r hpexpire h2 1 FIELDS 1 f1
after 5
assert_equal [r hrandfield h2 2] "f2"
# HRANDFIELD delete expired field and propagate DEL. Reply none.
r hset h3 f1 v1
r hpexpire h3 1 FIELDS 1 f1
after 5
assert_equal [r hrandfield h3 2] ""
assert_replication_stream $repl {
{select *}
{hset h1 f1 v1 f2 v2}
{hpexpireat h1 * FIELDS 2 f1 f2}
{multi}
{hdel h1 *}
{hdel h1 *}
{exec}
{hset h2 f1 v1 f2 v2}
{hpexpireat h2 * FIELDS 1 f1}
{hdel h2 f1}
{hset h3 f1 v1}
{hpexpireat h3 * FIELDS 1 f1}
{hdel h3 f1}
}
close_replication_stream $repl
r debug set-active-expire 1
} {OK} {needs:repl}
# Start another server to test replication of TTLs # Start another server to test replication of TTLs
start_server {tags {needs:repl external:skip}} { start_server {tags {needs:repl external:skip}} {
# Set the outer layer server as primary # Set the outer layer server as primary
...@@ -1161,4 +1188,3 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -1161,4 +1188,3 @@ start_server {tags {"external:skip needs:debug"}} {
} }
} }
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment