summaryrefslogtreecommitdiff
path: root/src/server.c
diff options
context:
space:
mode:
authorHarkrishn Patro <harkrisp@amazon.com>2022-12-06 22:26:56 -0800
committerGitHub <noreply@github.com>2022-12-07 08:26:56 +0200
commitc0267b3fa5808df475dec83c956b9a2bec112b90 (patch)
treefd3913a9b0d1fe8cbb08a0618ea719db886a9eb7 /src/server.c
parent8a315fc285fc54c678b97107a02ee1627f2c1ebf (diff)
downloadredis-c0267b3fa5808df475dec83c956b9a2bec112b90.tar.gz
Optimize client memory usage tracking operation while client eviction is disabled (#11348)
## Issue During the client input/output buffer processing, the memory usage is incrementally updated to keep track of clients going beyond a certain threshold `maxmemory-clients` to be evicted. However, this additional tracking activity leads to unnecessary CPU cycles wasted when no client-eviction is required. It is applicable in two cases. * `maxmemory-clients` is set to `0` which equates to no client eviction (applicable to all clients) * `CLIENT NO-EVICT` flag is set to `ON` which equates to a particular client not applicable for eviction. ## Solution * Disable client memory usage tracking during the read/write flow when `maxmemory-clients` is set to `0` or `client no-evict` is `on`. The memory usage is tracked only during the `clientCron` i.e. it gets periodically updated. * Cleanup the clients from the memory usage bucket when client eviction is disabled. * When the maxmemory-clients config is enabled or disabled at runtime, we immediately update the memory usage buckets for all clients (tested scanning 80000 took some 20ms) Benchmark shown that this can improve performance by about 5% in certain situations. Co-authored-by: Oran Agra <oran@redislabs.com>
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c135
1 files changed, 91 insertions, 44 deletions
diff --git a/src/server.c b/src/server.c
index 7bc320795..b4ea6f9e3 100644
--- a/src/server.c
+++ b/src/server.c
@@ -839,37 +839,44 @@ static inline clientMemUsageBucket *getMemUsageBucket(size_t mem) {
return &server.client_mem_usage_buckets[bucket_idx];
}
-/* This is called both on explicit clients when something changed their buffers,
- * so we can track clients' memory and enforce clients' maxmemory in real time,
- * and also from the clientsCron. We call it from the cron so we have updated
- * stats for non CLIENT_TYPE_NORMAL/PUBSUB clients and in case a configuration
- * change requires us to evict a non-active client.
+/*
+ * This method updates the client memory usage and update the
+ * server stats for client type.
*
- * This also adds the client to the correct memory usage bucket. Each bucket contains
- * all clients with roughly the same amount of memory. This way we group
- * together clients consuming about the same amount of memory and can quickly
- * free them in case we reach maxmemory-clients (client eviction).
+ * This method is called from the clientsCron to have updated
+ * stats for non CLIENT_TYPE_NORMAL/PUBSUB clients to accurately
+ * provide information around clients memory usage.
+ *
+ * It is also used in updateClientMemUsageAndBucket to have latest
+ * client memory usage information to place it into appropriate client memory
+ * usage bucket.
*/
-int updateClientMemUsage(client *c) {
- serverAssert(io_threads_op == IO_THREADS_OP_IDLE);
+void updateClientMemoryUsage(client *c) {
size_t mem = getClientMemoryUsage(c, NULL);
int type = getClientType(c);
+ /* Now that we have the memory used by the client, remove the old
+ * value from the old category, and add it back. */
+ server.stat_clients_type_memory[c->last_memory_type] -= c->last_memory_usage;
+ server.stat_clients_type_memory[type] += mem;
+ /* Remember what we added and where, to remove it next time. */
+ c->last_memory_type = type;
+ c->last_memory_usage = mem;
+}
- /* Remove the old value of the memory used by the client from the old
- * category, and add it back. */
- if (type != c->last_memory_type) {
- server.stat_clients_type_memory[c->last_memory_type] -= c->last_memory_usage;
- server.stat_clients_type_memory[type] += mem;
- c->last_memory_type = type;
- } else {
- server.stat_clients_type_memory[type] += mem - c->last_memory_usage;
+int clientEvictionAllowed(client *c) {
+ if (server.maxmemory_clients == 0 || c->flags & CLIENT_NO_EVICT) {
+ return 0;
}
+ int type = getClientType(c);
+ return (type == CLIENT_TYPE_NORMAL || type == CLIENT_TYPE_PUBSUB);
+}
- int allow_eviction =
- (type == CLIENT_TYPE_NORMAL || type == CLIENT_TYPE_PUBSUB) &&
- !(c->flags & CLIENT_NO_EVICT);
- /* Update the client in the mem usage buckets */
+/* This function is used to cleanup the client's previously tracked memory usage.
+ * This is called during incremental client memory usage tracking as well as
+ * used to reset when client to bucket allocation is not required when
+ * client eviction is disabled. */
+void removeClientFromMemUsageBucket(client *c, int allow_eviction) {
if (c->mem_usage_bucket) {
c->mem_usage_bucket->mem_usage_sum -= c->last_memory_usage;
/* If this client can't be evicted then remove it from the mem usage
@@ -880,23 +887,42 @@ int updateClientMemUsage(client *c) {
c->mem_usage_bucket_node = NULL;
}
}
- if (allow_eviction) {
- clientMemUsageBucket *bucket = getMemUsageBucket(mem);
- bucket->mem_usage_sum += mem;
- if (bucket != c->mem_usage_bucket) {
- if (c->mem_usage_bucket)
- listDelNode(c->mem_usage_bucket->clients,
- c->mem_usage_bucket_node);
- c->mem_usage_bucket = bucket;
- listAddNodeTail(bucket->clients, c);
- c->mem_usage_bucket_node = listLast(bucket->clients);
- }
+}
+
+/* This is called only if explicit clients when something changed their buffers,
+ * so we can track clients' memory and enforce clients' maxmemory in real time.
+ *
+ * This also adds the client to the correct memory usage bucket. Each bucket contains
+ * all clients with roughly the same amount of memory. This way we group
+ * together clients consuming about the same amount of memory and can quickly
+ * free them in case we reach maxmemory-clients (client eviction).
+ *
+ * returns 1 if client eviction for this client is allowed, 0 otherwise.
+ */
+int updateClientMemUsageAndBucket(client *c) {
+ serverAssert(io_threads_op == IO_THREADS_OP_IDLE);
+ int allow_eviction = clientEvictionAllowed(c);
+ removeClientFromMemUsageBucket(c, allow_eviction);
+
+ if (!allow_eviction) {
+ return 0;
}
- /* Remember what we added, to remove it next time. */
- c->last_memory_usage = mem;
+ /* Update client memory usage. */
+ updateClientMemoryUsage(c);
- return 0;
+ /* Update the client in the mem usage buckets */
+ clientMemUsageBucket *bucket = getMemUsageBucket(c->last_memory_usage);
+ bucket->mem_usage_sum += c->last_memory_usage;
+ if (bucket != c->mem_usage_bucket) {
+ if (c->mem_usage_bucket)
+ listDelNode(c->mem_usage_bucket->clients,
+ c->mem_usage_bucket_node);
+ c->mem_usage_bucket = bucket;
+ listAddNodeTail(bucket->clients, c);
+ c->mem_usage_bucket_node = listLast(bucket->clients);
+ }
+ return 1;
}
/* Return the max samples in the memory usage of clients tracked by
@@ -984,8 +1010,11 @@ void clientsCron(void) {
* 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 clientsCron() in
- * a more incremental way (depending on server.hz). */
- if (updateClientMemUsage(c)) continue;
+ * a more incremental way (depending on server.hz).
+ * If client eviction is enabled, update the bucket as well. */
+ if (!updateClientMemUsageAndBucket(c))
+ updateClientMemoryUsage(c);
+
if (closeClientOnOutputBufferLimitReached(c, 0)) continue;
}
}
@@ -1865,6 +1894,25 @@ void createSharedObjects(void) {
shared.maxstring = sdsnew("maxstring");
}
+void initServerClientMemUsageBuckets() {
+ if (server.client_mem_usage_buckets)
+ return;
+ server.client_mem_usage_buckets = zmalloc(sizeof(clientMemUsageBucket)*CLIENT_MEM_USAGE_BUCKETS);
+ for (int j = 0; j < CLIENT_MEM_USAGE_BUCKETS; j++) {
+ server.client_mem_usage_buckets[j].mem_usage_sum = 0;
+ server.client_mem_usage_buckets[j].clients = listCreate();
+ }
+}
+
+void freeServerClientMemUsageBuckets() {
+ if (!server.client_mem_usage_buckets)
+ return;
+ for (int j = 0; j < CLIENT_MEM_USAGE_BUCKETS; j++)
+ listRelease(server.client_mem_usage_buckets[j].clients);
+ zfree(server.client_mem_usage_buckets);
+ server.client_mem_usage_buckets = NULL;
+}
+
void initServerConfig(void) {
int j;
char *default_bindaddr[CONFIG_DEFAULT_BINDADDR_COUNT] = CONFIG_DEFAULT_BINDADDR;
@@ -2461,6 +2509,7 @@ void initServer(void) {
server.cluster_drop_packet_filter = -1;
server.reply_buffer_peak_reset_time = REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME;
server.reply_buffer_resizing_enabled = 1;
+ server.client_mem_usage_buckets = NULL;
resetReplicationBuffer();
/* Make sure the locale is set on startup based on the config file. */
@@ -2469,11 +2518,6 @@ void initServer(void) {
exit(1);
}
- for (j = 0; j < CLIENT_MEM_USAGE_BUCKETS; j++) {
- server.client_mem_usage_buckets[j].mem_usage_sum = 0;
- server.client_mem_usage_buckets[j].clients = listCreate();
- }
-
createSharedObjects();
adjustOpenFilesLimit();
const char *clk_msg = monotonicInit();
@@ -2606,6 +2650,9 @@ void initServer(void) {
ACLUpdateDefaultUserPassword(server.requirepass);
applyWatchdogPeriod();
+
+ if (server.maxmemory_clients != 0)
+ initServerClientMemUsageBuckets();
}
void initListeners() {