From 897c3d522c6814b1853b767795a18565c047133e Mon Sep 17 00:00:00 2001 From: Chen Tianjie Date: Thu, 23 Feb 2023 15:07:49 +0800 Subject: [PATCH] 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 Co-authored-by: Madelyn Olson <34459052+madolson@users.noreply.github.com> Co-authored-by: sundb --- src/commands.c | 22 +++++++++++++++++++ src/commands/client-no-touch.json | 36 +++++++++++++++++++++++++++++++ src/db.c | 3 +++ src/networking.c | 20 ++++++++++++++--- src/server.h | 1 + tests/unit/introspection-2.tcl | 25 +++++++++++++++++++++ 6 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/commands/client-no-touch.json diff --git a/src/commands.c b/src/commands.c index 7698f2055..e24ab9dfc 100644 --- a/src/commands.c +++ b/src/commands.c @@ -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}, diff --git a/src/commands/client-no-touch.json b/src/commands/client-no-touch.json new file mode 100644 index 000000000..71292e952 --- /dev/null +++ b/src/commands/client-no-touch.json @@ -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" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/db.c b/src/db.c index e1d28a3e4..ddfb04ebb 100644 --- a/src/db.c +++ b/src/db.c @@ -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); diff --git a/src/networking.c b/src/networking.c index 117c54a96..2edb7b72a 100644 --- a/src/networking.c +++ b/src/networking.c @@ -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); } diff --git a/src/server.h b/src/server.h index 11629c718..7d8e65b36 100644 --- a/src/server.h +++ b/src/server.h @@ -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. */ diff --git a/tests/unit/introspection-2.tcl b/tests/unit/introspection-2.tcl index 9576c39ab..2992f6aa3 100644 --- a/tests/unit/introspection-2.tcl +++ b/tests/unit/introspection-2.tcl @@ -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