Merge 61153de0bc30be830ab88ac680fc4e27f11d54b6 into 26c6f1af9b29d525831c7fa9840ab3e47ed7b700

This commit is contained in:
Arcadiy Ivanov 2025-02-02 18:01:31 +02:00 committed by GitHub
commit 0a2f808623
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 375 additions and 6 deletions

View File

@ -10509,6 +10509,30 @@ struct COMMAND_ARG GETEX_Args[] = {
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=GETEX_expiration_Subargs},
};
/********** GETPXT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* GETPXT history */
#define GETPXT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* GETPXT tips */
#define GETPXT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* GETPXT key specs */
keySpec GETPXT_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* GETPXT argument table */
struct COMMAND_ARG GETPXT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** GETRANGE ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@ -10689,6 +10713,32 @@ struct COMMAND_ARG MGET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** MGETPXT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* MGETPXT history */
#define MGETPXT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* MGETPXT tips */
const char *MGETPXT_Tips[] = {
"request_policy:multi_shard",
};
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* MGETPXT key specs */
keySpec MGETPXT_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}}
};
#endif
/* MGETPXT argument table */
struct COMMAND_ARG MGETPXT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** MSET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@ -11287,6 +11337,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
{MAKE_CMD("get","Returns the string value of a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GET_History,0,GET_Tips,0,getCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,GET_Keyspecs,1,NULL,1),.args=GET_Args},
{MAKE_CMD("getdel","Returns the string value of a key after deleting the key.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETDEL_History,0,GETDEL_Tips,0,getdelCommand,2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,GETDEL_Keyspecs,1,NULL,1),.args=GETDEL_Args},
{MAKE_CMD("getex","Returns the string value of a key after setting its expiration time.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETEX_History,0,GETEX_Tips,0,getexCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,GETEX_Keyspecs,1,NULL,2),.args=GETEX_Args},
{MAKE_CMD("getpxt","Returns the string value of a key and the expiration time as a Unix milliseconds timestamp, if set.","O(1)","8.0.2",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETPXT_History,0,GETPXT_Tips,0,getpxtCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING|ACL_CATEGORY_KEYSPACE,GETPXT_Keyspecs,1,NULL,1),.args=GETPXT_Args},
{MAKE_CMD("getrange","Returns a substring of the string stored at a key.","O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.","2.4.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETRANGE_History,0,GETRANGE_Tips,0,getrangeCommand,4,CMD_READONLY,ACL_CATEGORY_STRING,GETRANGE_Keyspecs,1,NULL,3),.args=GETRANGE_Args},
{MAKE_CMD("getset","Returns the previous string value of a key after setting it to a new value.","O(1)","1.0.0",CMD_DOC_DEPRECATED,"`SET` with the `!GET` argument","6.2.0","string",COMMAND_GROUP_STRING,GETSET_History,0,GETSET_Tips,0,getsetCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,GETSET_Keyspecs,1,NULL,2),.args=GETSET_Args},
{MAKE_CMD("incr","Increments the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCR_History,0,INCR_Tips,0,incrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCR_Keyspecs,1,NULL,1),.args=INCR_Args},
@ -11294,6 +11345,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
{MAKE_CMD("incrbyfloat","Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBYFLOAT_History,0,INCRBYFLOAT_Tips,0,incrbyfloatCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCRBYFLOAT_Keyspecs,1,NULL,2),.args=INCRBYFLOAT_Args},
{MAKE_CMD("lcs","Finds the longest common substring.","O(N*M) where N and M are the lengths of s1 and s2, respectively","7.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,LCS_History,0,LCS_Tips,0,lcsCommand,-3,CMD_READONLY,ACL_CATEGORY_STRING,LCS_Keyspecs,1,NULL,6),.args=LCS_Args},
{MAKE_CMD("mget","Atomically returns the string values of one or more keys.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGET_History,0,MGET_Tips,1,mgetCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,MGET_Keyspecs,1,NULL,1),.args=MGET_Args},
{MAKE_CMD("mgetpxt","Atomically returns the string values of one or more keys and their millisecond expiration, if available.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGETPXT_History,0,MGETPXT_Tips,1,mgetpxtCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING|ACL_CATEGORY_KEYSPACE,MGETPXT_Keyspecs,1,NULL,1),.args=MGETPXT_Args},
{MAKE_CMD("mset","Atomically creates or modifies the string values of one or more keys.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSET_History,0,MSET_Tips,2,msetCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSET_Keyspecs,1,NULL,1),.args=MSET_Args},
{MAKE_CMD("msetnx","Atomically modifies the string values of one or more keys only when all keys don't exist.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSETNX_History,0,MSETNX_Tips,0,msetnxCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSETNX_Keyspecs,1,NULL,1),.args=MSETNX_Args},
{MAKE_CMD("psetex","Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.","O(1)","2.6.0",CMD_DOC_DEPRECATED,"`SET` with the `PX` argument","2.6.12","string",COMMAND_GROUP_STRING,PSETEX_History,0,PSETEX_Tips,0,psetexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,PSETEX_Keyspecs,1,NULL,3),.args=PSETEX_Args},

80
src/commands/getpxt.json Normal file
View File

@ -0,0 +1,80 @@
{
"GETPXT": {
"summary": "Returns the string value of a key and the expiration time as a Unix milliseconds timestamp, if set.",
"complexity": "O(1)",
"group": "string",
"since": "8.0.2",
"arity": 2,
"function": "getpxtCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"STRING",
"KEYSPACE"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"description": "The value of the key.",
"type": "string"
},
{
"oneOf": [
{
"type": "integer",
"description": "Expiration Unix timestamp in milliseconds.",
"minimum": 0
},
{
"const": -1,
"description": "The key exists but has no associated expiration time."
}
]
}
]
}
},
{
"description": "Key does not exist.",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}

88
src/commands/mgetpxt.json Normal file
View File

@ -0,0 +1,88 @@
{
"MGETPXT": {
"summary": "Atomically returns the string values of one or more keys and their millisecond expiration, if available.",
"complexity": "O(N) where N is the number of keys to retrieve.",
"group": "string",
"since": "1.0.0",
"arity": -2,
"function": "mgetpxtCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"STRING",
"KEYSPACE"
],
"command_tips": [
"REQUEST_POLICY:MULTI_SHARD"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": -1,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "List of values at the specified keys.",
"type": "array",
"minItems": 1,
"items": {
"oneOf": [
{
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"description": "The value of the key.",
"type": "string"
},
{
"oneOf": [
{
"type": "integer",
"description": "Expiration Unix timestamp in milliseconds.",
"minimum": 0
},
{
"const": -1,
"description": "The key exists but has no associated expiration time."
}
]
}
]
}
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0,
"multiple": true
}
]
}
}

View File

@ -3623,6 +3623,7 @@ void psetexCommand(client *c);
void getCommand(client *c);
void getexCommand(client *c);
void getdelCommand(client *c);
void getpxtCommand(client *c);
void delCommand(client *c);
void unlinkCommand(client *c);
void existsCommand(client *c);
@ -3695,6 +3696,7 @@ void rpoplpushCommand(client *c);
void lmoveCommand(client *c);
void infoCommand(client *c);
void mgetCommand(client *c);
void mgetpxtCommand(client *c);
void monitorCommand(client *c);
void expireCommand(client *c);
void expireatCommand(client *c);

View File

@ -393,6 +393,30 @@ void getCommand(client *c) {
getGenericCommand(c);
}
void getpxtCommand(client *c) {
long long expire;
robj *o;
if ((o = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp])) == NULL)
return;
if (checkType(c, o, OBJ_STRING)) {
return;
}
addReplyArrayLen(c, 2);
addReplyBulk(c, o);
/* The key exists. Return -1 if it has no expire, or the actual
* expire value otherwise. */
expire = getExpire(c->db, c->argv[1]);
if (expire == -1) {
addReplyLongLong(c, -1);
} else {
addReplyLongLong(c, expire);
}
}
/*
* GETEX <key> [PERSIST][EX seconds][PX milliseconds][EXAT seconds-timestamp][PXAT milliseconds-timestamp]
*
@ -608,6 +632,34 @@ void mgetCommand(client *c) {
}
}
void mgetpxtCommand(client *c) {
int j;
addReplyArrayLen(c, c->argc - 1);
for (j = 1; j < c->argc; j++) {
robj *o = lookupKeyRead(c->db, c->argv[j]);
if (o == NULL) {
addReplyNull(c);
} else {
if (o->type != OBJ_STRING) {
addReplyNull(c);
} else {
addReplyArrayLen(c, 2);
addReplyBulk(c, o);
/* The key exists. Return -1 if it has no expire, or the actual
* expire value otherwise. */
long long expire = getExpire(c->db, c->argv[j]);
if (expire == -1) {
addReplyLongLong(c, -1);
} else {
addReplyLongLong(c, expire);
}
}
}
}
}
void msetGenericCommand(client *c, int nx) {
int j;

View File

@ -138,6 +138,7 @@ typedef struct _client {
redisContext *context;
sds obuf;
char **randptr; /* Pointers to :rand: strings inside the command buf */
bool *randlast; /* Pointer to flag indicating whether :rand: should be last used or new */
size_t randlen; /* Number of pointers in client->randptr */
size_t randfree; /* Number of unused pointers in client->randptr */
char **stagptr; /* Pointers to slot hashtags (cluster mode only) */
@ -361,6 +362,7 @@ static void freeClient(client c) {
redisFree(c->context);
sdsfree(c->obuf);
zfree(c->randptr);
zfree(c->randlast);
zfree(c->stagptr);
zfree(c);
if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex));
@ -392,11 +394,18 @@ static void resetClient(client c) {
static void randomizeClientKey(client c) {
size_t i;
size_t last_rand = 0;
for (i = 0; i < c->randlen; i++) {
char *p = c->randptr[i] + 11;
size_t r = 0;
if (config.randomkeys_keyspacelen != 0) r = random() % config.randomkeys_keyspacelen;
// If last random is requested then use the last random
if(c->randlast[i]) {
r = last_rand;
} else if (config.randomkeys_keyspacelen != 0) {
r = random() % config.randomkeys_keyspacelen;
last_rand = r;
}
size_t j;
for (j = 0; j < 12; j++) {
@ -741,6 +750,7 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
c->written = 0;
c->pending = config.pipeline + c->prefix_pending;
c->randptr = NULL;
c->randlast = NULL;
c->randlen = 0;
c->stagptr = NULL;
c->staglen = 0;
@ -751,11 +761,13 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
c->randlen = from->randlen;
c->randfree = 0;
c->randptr = zmalloc(sizeof(char *) * c->randlen);
c->randlast = zmalloc(sizeof(bool) * c->randlen);
/* copy the offsets. */
for (j = 0; j < (int)c->randlen; j++) {
c->randptr[j] = c->obuf + (from->randptr[j] - from->obuf);
/* Adjust for the different select prefix length. */
c->randptr[j] += c->prefixlen - from->prefixlen;
c->randlast[j] = from->randlast[j];
}
} else {
char *p = c->obuf;
@ -763,14 +775,23 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
c->randlen = 0;
c->randfree = RANDPTR_INITIAL_SIZE;
c->randptr = zmalloc(sizeof(char *) * c->randfree);
while ((p = strstr(p, "__rand_int__")) != NULL) {
c->randlast = zmalloc(sizeof(bool) * c->randfree);
while ((p = strstr(p, "__rand_")) != NULL) {
bool rand_int = !(bool)strncmp("__rand_int__", p, 12);
bool rand_last = !(bool)strncmp("__rand_lst__", p, 12);
if(!rand_int && ! rand_last)
continue;
if (c->randfree == 0) {
c->randptr = zrealloc(c->randptr, sizeof(char *) * c->randlen * 2);
c->randlast = zrealloc(c->randlast, sizeof(bool) * c->randlen * 2);
c->randfree += c->randlen;
}
c->randptr[c->randlen++] = p;
c->randptr[c->randlen] = p;
c->randlast[c->randlen] = rand_last;
c->randlen++;
c->randfree--;
p += 12; /* 12 is strlen("__rand_int__). */
p += 12; /* 12 is strlen("__rand_int__), same for "__rand_lst__". */
}
}
}
@ -1569,8 +1590,10 @@ usage:
" $ valkey-benchmark -r 10000 -n 10000 eval 'return redis.call(\"ping\")' 0\n\n"
" Fill a list with 10000 random elements:\n"
" $ valkey-benchmark -r 10000 -n 10000 lpush mylist __rand_int__\n\n"
" On user specified command lines __rand_int__ is replaced with a random integer\n"
" with a range of values selected by the -r option.\n");
" On user-specified command lines __rand_int__ is replaced with a random integer\n"
" with a range of values selected by the -r option.\n"
" On user-specified command lines __rand_lst__ is replaced with the last value\n"
" of __rand_int__ or zero if no such value is available.\n");
exit(exit_status);
}
@ -1958,6 +1981,39 @@ int main(int argc, char **argv) {
free(cmd);
}
if (test_is_selected("set_pxat")) {
len = redisFormatCommand(&cmd, "SET key%s:__rand_int__ %s PXAT 17344823940230", tag, data);
benchmark("SET w/ PXAT", cmd, len);
free(cmd);
}
if (test_is_selected("getpxt")) {
len = redisFormatCommand(&cmd, "GETPXT key%s:__rand_int__", tag);
benchmark("GETPXT", cmd, len);
free(cmd);
}
if (test_is_selected("getpxt_simulated")) {
redisFormatCommand(&cmd, "MULTI");
sds pipeline = sdscatprintf(sdsempty(), "%s", cmd);
free(cmd);
redisFormatCommand(&cmd, "GET key%s:__rand_int__", tag);
pipeline = sdscatprintf(pipeline, "%s", cmd);
free(cmd);
redisFormatCommand(&cmd, "PEXPIRETIME key%s:__rand_lst__", tag);
pipeline = sdscatprintf(pipeline, "%s", cmd);
free(cmd);
redisFormatCommand(&cmd, "EXEC");
pipeline = sdscatprintf(pipeline, "%s", cmd);
free(cmd);
benchmark("GETPXT Simulated", pipeline, sdslen(pipeline));
sdsfree(pipeline);
}
if (!config.csv) printf("\n");
} while (config.loop);

View File

@ -214,6 +214,28 @@ start_server {tags {"string"}} {
r mget foo{t} baazz{t} bar{t} myset{t}
} {BAR {} FOO {}}
test {MGETPXT} {
r flushdb
r set foo{t} BAR pxat 17344823940230
r set bar{t} FOO pxat 17344823940231
r mgetpxt foo{t} bar{t}
} {{BAR 17344823940230} {FOO 17344823940231}}
test {MGETPXT against non existing key} {
r mgetpxt foo{t} baazz{t} bar{t}
} {{BAR 17344823940230} {} {FOO 17344823940231}}
test {MGETPXT against non-string key} {
r sadd myset{t} ciao
r sadd myset{t} bau
r mgetpxt foo{t} baazz{t} bar{t} myset{t}
} {{BAR 17344823940230} {} {FOO 17344823940231} {}}
test {MGETPXT against a key with no expiration} {
r set baz{t} BAZ
r mgetpxt foo{t} baz{t} bar{t}
} {{BAR 17344823940230} {BAZ -1} {FOO 17344823940231}}
test {GETSET (set new value)} {
r del foo
list [r getset foo xyz] [r get foo]
@ -658,6 +680,23 @@ if {[string match {*jemalloc*} [s mem_allocator]]} {
assert_range [r ttl foo] 5 10
}
test "GETPXT after SET PXAT" {
r del foo
r set foo bar pxat 17344823940230
r getpxt foo
} {bar 17344823940230}
test "GETPXT after SET with no expiration" {
r del foo
r set foo bar
r getpxt foo
} {bar -1}
test "GETPXT with no entry" {
r del foo
r getpxt foo
} {}
test "SET EXAT / PXAT Expiration time is expired" {
r debug set-active-expire 0
set repl [attach_to_replication_stream]