Add CLIENT NO-TOUCH for clients to run commands without affecting LRU/LFU of keys (#11483)

When no-touch mode is enabled, the client will not touch LRU/LFU of the
keys it accesses, except when executing command `TOUCH`.
This allows inspecting or modifying the key-space without affecting their eviction.

Changes:
- A command `CLIENT NO-TOUCH ON|OFF` to switch on and off this mode.
- A client flag `#define CLIENT_NOTOUCH (1ULL<<45)`, which can be shown
  with `CLIENT INFO`, by the letter "T" in the "flags" field.
- Clear `NO-TOUCH` flag in `clearClientConnectionState`, which is used by `RESET`
  command and resetting temp clients used by modules.
- Also clear `NO-EVICT` flag in `clearClientConnectionState`, this might have been an
  oversight, spotted by @madolson.
- A test using `DEBUG OBJECT` command to verify that LRU stat is not touched when
  no-touch mode is on.
 

Co-authored-by: chentianjie <chentianjie@alibaba-inc.com>
Co-authored-by: Madelyn Olson <34459052+madolson@users.noreply.github.com>
Co-authored-by: sundb <sundbcn@gmail.com>
This commit is contained in:
Chen Tianjie 2023-02-23 15:07:49 +08:00 committed by GitHub
parent cd58af4d7f
commit 897c3d522c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 3 deletions

View File

@ -890,6 +890,27 @@ struct redisCommandArg CLIENT_NO_EVICT_Args[] = {
{0}
};
/********** CLIENT NO_TOUCH ********************/
/* CLIENT NO_TOUCH history */
#define CLIENT_NO_TOUCH_History NULL
/* CLIENT NO_TOUCH tips */
#define CLIENT_NO_TOUCH_tips NULL
/* CLIENT NO_TOUCH enabled argument table */
struct redisCommandArg CLIENT_NO_TOUCH_enabled_Subargs[] = {
{"on",ARG_TYPE_PURE_TOKEN,-1,"ON",NULL,NULL,CMD_ARG_NONE},
{"off",ARG_TYPE_PURE_TOKEN,-1,"OFF",NULL,NULL,CMD_ARG_NONE},
{0}
};
/* CLIENT NO_TOUCH argument table */
struct redisCommandArg CLIENT_NO_TOUCH_Args[] = {
{"enabled",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,.subargs=CLIENT_NO_TOUCH_enabled_Subargs},
{0}
};
/********** CLIENT PAUSE ********************/
/* CLIENT PAUSE history */
@ -1027,6 +1048,7 @@ struct redisCommand CLIENT_Subcommands[] = {
{"kill","Kill the connection of a client","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,CLIENT_KILL_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_KILL_Args},
{"list","Get the list of client connections","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,CLIENT_LIST_tips,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_LIST_Args},
{"no-evict","Set client eviction mode for the current connection","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,CLIENT_NO_EVICT_tips,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_NO_EVICT_Args},
{"no-touch","Controls whether commands sent by the client will alter the LRU/LFU of the keys they access.","O(1)",NULL,CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,CLIENT_NO_TOUCH_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_NO_TOUCH_Args},
{"pause","Stop processing commands from clients for some time","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,CLIENT_PAUSE_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_PAUSE_Args},
{"reply","Instruct the server whether to reply to commands","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_REPLY_History,CLIENT_REPLY_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_REPLY_Args},
{"setname","Set the current connection name","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_SETNAME_History,CLIENT_SETNAME_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_SETNAME_Args},

View File

@ -0,0 +1,36 @@
{
"NO-TOUCH": {
"summary": "Controls whether commands sent by the client will alter the LRU/LFU of the keys they access.",
"complexity": "O(1)",
"group": "connection",
"arity": 3,
"container": "CLIENT",
"function": "clientCommand",
"command_flags": [
"NOSCRIPT",
"LOADING",
"STALE"
],
"acl_categories": [
"CONNECTION"
],
"arguments": [
{
"name": "enabled",
"type": "oneof",
"arguments": [
{
"name": "on",
"type": "pure-token",
"token": "ON"
},
{
"name": "off",
"type": "pure-token",
"token": "OFF"
}
]
}
]
}
}

View File

@ -113,6 +113,9 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (server.current_client && server.current_client->flags & CLIENT_NO_TOUCH &&
server.current_client->cmd->proc != touchCommand)
flags |= LOOKUP_NOTOUCH;
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val);

View File

@ -1493,8 +1493,8 @@ void clearClientConnectionState(client *c) {
}
/* Selectively clear state flags not covered above */
c->flags &= ~(CLIENT_ASKING|CLIENT_READONLY|CLIENT_PUBSUB|
CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP_NEXT);
c->flags &= ~(CLIENT_ASKING|CLIENT_READONLY|CLIENT_PUBSUB|CLIENT_REPLY_OFF|
CLIENT_REPLY_SKIP_NEXT|CLIENT_NO_TOUCH|CLIENT_NO_EVICT);
}
void freeClient(client *c) {
@ -2692,7 +2692,7 @@ char *getClientSockname(client *c) {
/* Concatenate a string representing the state of a client in a human
* readable format, into the sds string 's'. */
sds catClientInfoString(sds s, client *client) {
char flags[16], events[3], conninfo[CONN_INFO_LEN], *p;
char flags[17], events[3], conninfo[CONN_INFO_LEN], *p;
p = flags;
if (client->flags & CLIENT_SLAVE) {
@ -2715,6 +2715,7 @@ sds catClientInfoString(sds s, client *client) {
if (client->flags & CLIENT_UNIX_SOCKET) *p++ = 'U';
if (client->flags & CLIENT_READONLY) *p++ = 'r';
if (client->flags & CLIENT_NO_EVICT) *p++ = 'e';
if (client->flags & CLIENT_NO_TOUCH) *p++ = 'T';
if (p == flags) *p++ = 'N';
*p++ = '\0';
@ -2905,6 +2906,8 @@ void clientCommand(client *c) {
" Report tracking status for the current connection.",
"NO-EVICT (ON|OFF)",
" Protect current client connection from eviction.",
"NO-TOUCH (ON|OFF)",
" Will not touch LRU/LFU stats when this mode is on.",
NULL
};
addReplyHelp(c, help);
@ -3374,6 +3377,17 @@ NULL
} else {
addReplyArrayLen(c,0);
}
} else if (!strcasecmp(c->argv[1]->ptr, "no-touch")) {
/* CLIENT NO-TOUCH ON|OFF */
if (!strcasecmp(c->argv[2]->ptr,"on")) {
c->flags |= CLIENT_NO_TOUCH;
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
c->flags &= ~CLIENT_NO_TOUCH;
addReply(c,shared.ok);
} else {
addReplyErrorObject(c,shared.syntaxerr);
}
} else {
addReplySubcommandSyntaxError(c);
}

View File

@ -390,6 +390,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
memory eviction. */
#define CLIENT_ALLOW_OOM (1ULL<<44) /* Client used by RM_Call is allowed to fully execute
scripts even when in OOM */
#define CLIENT_NO_TOUCH (1ULL<<45) /* This client will not touch LFU/LRU stats. */
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */

View File

@ -2,6 +2,17 @@ proc cmdstat {cmd} {
return [cmdrstat $cmd r]
}
proc getlru {key} {
set objinfo [r debug object $key]
foreach info $objinfo {
set kvinfo [split $info ":"]
if {[string compare [lindex $kvinfo 0] "lru"] == 0} {
return [lindex $kvinfo 1]
}
}
fail "Can't get LRU info with DEBUG OBJECT"
}
start_server {tags {"introspection"}} {
test {The microsecond part of the TIME command will not overflow} {
set now [r time]
@ -26,6 +37,20 @@ start_server {tags {"introspection"}} {
assert {[r object idletime foo] < 2}
}
test {Operations in no-touch mode do not alter the last access time of a key} {
r set foo bar
r client no-touch on
set oldlru [getlru foo]
after 1000
r get foo
set newlru [getlru foo]
assert_equal $newlru $oldlru
r client no-touch off
r get foo
set newlru [getlru foo]
assert_morethan $newlru $oldlru
} {} {needs:debug}
test {TOUCH returns the number of existing keys specified} {
r flushdb
r set key1{t} 1