diff --git a/keydb.conf b/keydb.conf index f588c634d..6ce43fdc9 100644 --- a/keydb.conf +++ b/keydb.conf @@ -943,13 +943,20 @@ replica-priority 100 # In all the above cases the default is to delete objects in a blocking way, # like if DEL was called. However you can configure each case specifically # in order to instead release memory in a non-blocking way like if UNLINK -# was called, using the following configuration directives: +# was called, using the following configuration directives. lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: + +lazyfree-lazy-user-del no + ################################ THREADED I/O ################################# # Redis is mostly single threaded, however there are certain threaded diff --git a/src/acl.cpp b/src/acl.cpp index 24d1e9f64..2f9d69199 100644 --- a/src/acl.cpp +++ b/src/acl.cpp @@ -1849,18 +1849,18 @@ void aclCommand(client *c) { } } else if (!strcasecmp(sub,"help")) { const char *help[] = { -"LOAD -- Reload users from the ACL file.", -"SAVE -- Save the current config to the ACL file." -"LIST -- Show user details in config file format.", -"USERS -- List all the registered usernames.", -"SETUSER [attribs ...] -- Create or modify a user.", -"GETUSER -- Get the user details.", -"DELUSER [...] -- Delete a list of users.", -"CAT -- List available categories.", -"CAT -- List commands inside category.", -"GENPASS -- Generate a secure user password.", -"WHOAMI -- Return the current connection username.", -"LOG [ | RESET] -- Show the ACL log entries.", +"LOAD -- Reload users from the ACL file.", +"SAVE -- Save the current config to the ACL file.", +"LIST -- Show user details in config file format.", +"USERS -- List all the registered usernames.", +"SETUSER [attribs ...] -- Create or modify a user.", +"GETUSER -- Get the user details.", +"DELUSER [...] -- Delete a list of users.", +"CAT -- List available categories.", +"CAT -- List commands inside category.", +"GENPASS -- Generate a secure user password.", +"WHOAMI -- Return the current connection username.", +"LOG [ | RESET] -- Show the ACL log entries.", NULL }; addReplyHelp(c,help); diff --git a/src/db.cpp b/src/db.cpp index 6c9162fc9..195fe6227 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -660,7 +660,7 @@ void delGenericCommand(client *c, int lazy) { } void delCommand(client *c) { - delGenericCommand(c,0); + delGenericCommand(c,g_pserver->lazyfree_lazy_user_del); } void unlinkCommand(client *c) { diff --git a/src/networking.cpp b/src/networking.cpp index f43dea60b..a4c70b4f5 100644 --- a/src/networking.cpp +++ b/src/networking.cpp @@ -178,6 +178,8 @@ client *createClient(connection *conn, int iel) { memset(c->uuid, 0, UUID_BINARY_LEN); c->client_tracking_prefixes = NULL; + c->client_cron_last_memory_usage = 0; + c->client_cron_last_memory_type = CLIENT_TYPE_NORMAL; c->auth_callback = NULL; c->auth_callback_privdata = NULL; c->auth_module = NULL; @@ -1577,6 +1579,11 @@ bool freeClient(client *c) { listDelNode(g_pserver->clients_to_close,ln); } + /* Remove the contribution that this client gave to our + * incrementally computed memory usage. */ + g_pserver->stat_clients_type_memory[c->client_cron_last_memory_type] -= + c->client_cron_last_memory_usage; + /* Release other dynamically allocated client structure fields, * and finally release the client structure itself. */ zfree(c->bufAsync); diff --git a/src/object.cpp b/src/object.cpp index 7d07d2de4..2954e19e2 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -1025,32 +1025,15 @@ struct redisMemOverhead *getMemoryOverheadData(void) { mh->repl_backlog = mem; mem_total += mem; - mem = 0; - if (listLength(g_pserver->clients)) { - listIter li; - listNode *ln; - size_t mem_normal = 0, mem_slaves = 0; - - listRewind(g_pserver->clients,&li); - while((ln = listNext(&li))) { - size_t mem_curr = 0; - client *c = (client*)listNodeValue(ln); - std::unique_lock ul(c->lock); - - int type = getClientType(c); - mem_curr += getClientOutputBufferMemoryUsage(c); - mem_curr += sdsAllocSize(c->querybuf); - mem_curr += sizeof(client); - if (type == CLIENT_TYPE_SLAVE) - mem_slaves += mem_curr; - else - mem_normal += mem_curr; - } - mh->clients_slaves = mem_slaves; - mh->clients_normal = mem_normal; - mem = mem_slaves + mem_normal; - } - mem_total+=mem; + /* Computing the memory used by the clients would be O(N) if done + * here online. We use our values computed incrementally by + * clientsCronTrackClientsMemUsage(). */ + mh->clients_slaves = g_pserver->stat_clients_type_memory[CLIENT_TYPE_SLAVE]; + mh->clients_normal = g_pserver->stat_clients_type_memory[CLIENT_TYPE_MASTER]+ + g_pserver->stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+ + g_pserver->stat_clients_type_memory[CLIENT_TYPE_NORMAL]; + mem_total += mh->clients_slaves; + mem_total += mh->clients_normal; mem = 0; if (g_pserver->aof_state != AOF_OFF) { diff --git a/src/rdb.cpp b/src/rdb.cpp index 10c22e31f..5db73aca7 100644 --- a/src/rdb.cpp +++ b/src/rdb.cpp @@ -1236,10 +1236,7 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) { if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr; if (rdbSaveLen(rdb,j) == -1) goto werr; - /* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which - * is currently the largest type we are able to represent in RDB sizes. - * However this does not limit the actual size of the DB to load since - * these sizes are just hints to resize the hash tables. */ + /* Write the RESIZE DB opcode. */ uint64_t db_size, expires_size; db_size = dictSize(db->pdict); expires_size = db->setexpire->size(); diff --git a/src/scripting.cpp b/src/scripting.cpp index f84c7386f..ae483e286 100644 --- a/src/scripting.cpp +++ b/src/scripting.cpp @@ -972,6 +972,7 @@ int luaLogCommand(lua_State *lua) { lua_pushstring(lua, "Invalid debug level."); return lua_error(lua); } + if (level < cserver.verbosity) return 0; /* Glue together all the arguments */ log = sdsempty(); diff --git a/src/server.cpp b/src/server.cpp index dd976b841..3fe980b2b 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1632,6 +1632,28 @@ int clientsCronTrackExpansiveClients(client *c) { return 0; /* This function never terminates the client. */ } +/* Iterating all the clients in getMemoryOverheadData() is too slow and + * in turn would make the INFO command too slow. So we perform this + * computation incrementally and track the (not instantaneous but updated + * to the second) total memory used by clients using clinetsCron() in + * a more incremental way (depending on server.hz). */ +int clientsCronTrackClientsMemUsage(client *c) { + size_t mem = 0; + int type = getClientType(c); + mem += getClientOutputBufferMemoryUsage(c); + mem += sdsAllocSize(c->querybuf); + mem += sizeof(client); + /* Now that we have the memory used by the client, remove the old + * value from the old categoty, and add it back. */ + g_pserver->stat_clients_type_memory[c->client_cron_last_memory_type] -= + c->client_cron_last_memory_usage; + g_pserver->stat_clients_type_memory[type] += mem; + /* Remember what we added and where, to remove it next time. */ + c->client_cron_last_memory_usage = mem; + c->client_cron_last_memory_type = type; + return 0; +} + /* Return the max samples in the memory usage of clients tracked by * the function clientsCronTrackExpansiveClients(). */ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) { @@ -1695,6 +1717,7 @@ void clientsCron(int iel) { if (clientsCronHandleTimeout(c,now)) continue; // Client free'd so don't release the lock if (clientsCronResizeQueryBuffer(c)) goto LContinue; if (clientsCronTrackExpansiveClients(c)) goto LContinue; + if (clientsCronTrackClientsMemUsage(c)) goto LContinue; LContinue: fastlock_unlock(&c->lock); } @@ -3042,6 +3065,8 @@ void initServer(void) { g_pserver->stat_rdb_cow_bytes = 0; g_pserver->stat_aof_cow_bytes = 0; g_pserver->stat_module_cow_bytes = 0; + for (int j = 0; j < CLIENT_TYPE_COUNT; j++) + g_pserver->stat_clients_type_memory[j] = 0; g_pserver->cron_malloc_stats.zmalloc_used = 0; g_pserver->cron_malloc_stats.process_rss = 0; g_pserver->cron_malloc_stats.allocator_allocated = 0; diff --git a/src/server.h b/src/server.h index cd00aaa88..709da0f04 100644 --- a/src/server.h +++ b/src/server.h @@ -450,6 +450,7 @@ public: #define CLIENT_TYPE_SLAVE 1 /* Slaves. */ #define CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */ #define CLIENT_TYPE_MASTER 3 /* Master. */ +#define CLIENT_TYPE_COUNT 4 /* Total number of client types. */ #define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output buffer configuration. Just the first three: normal, replica, pubsub. */ @@ -1292,10 +1293,10 @@ typedef struct client { * when the authenticated user * changes. */ void *auth_callback_privdata; /* Private data that is passed when the auth - * changed callback is executed. Opaque for + * changed callback is executed. Opaque for * Redis Core. */ void *auth_module; /* The module that owns the callback, which is used - * to disconnect the client if the module is + * to disconnect the client if the module is * unloaded for cleanup. Opaque for Redis Core.*/ /* UUID announced by the client (default nil) - used to detect multiple connections to/from the same peer */ @@ -1310,6 +1311,13 @@ typedef struct client { rax *client_tracking_prefixes; /* A dictionary of prefixes we are already subscribed to in BCAST mode, in the context of client side caching. */ + /* In clientsCronTrackClientsMemUsage() we track the memory usage of + * each client and add it to the sum of all the clients of a given type, + * however we need to remember what was the old contribution of each + * client, and in which categoty the client was, in order to remove it + * before adding it the new value. */ + uint64_t client_cron_last_memory_usage; + int client_cron_last_memory_type; /* Response buffer */ int bufpos; char buf[PROTO_REPLY_CHUNK_BYTES]; @@ -1709,6 +1717,7 @@ struct redisServer { size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */ size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */ size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */ + uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */ long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */ /* The following two are used to track instantaneous metrics, like * number of operations per second, network traffic. */ @@ -1940,6 +1949,7 @@ struct redisServer { int lazyfree_lazy_eviction; int lazyfree_lazy_expire; int lazyfree_lazy_server_del; + int lazyfree_lazy_user_del; /* Latency monitor */ long long latency_monitor_threshold; dict *latency_events;