From 1bc6005d519372234452cb730403f908d3f3f8b3 Mon Sep 17 00:00:00 2001 From: hwware Date: Fri, 26 Jul 2024 15:04:52 +0000 Subject: [PATCH 01/12] Add maxmemory-reserved-scale parameter for evicting key earlier Signed-off-by: hwware --- src/config.c | 16 ++++++++++++++++ src/evict.c | 28 +++++++++++++++++++++++----- src/server.c | 10 ++++++++++ src/server.h | 2 ++ valkey.conf | 7 +++++++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/config.c b/src/config.c index c4009adefa..aa3e7db9d0 100644 --- a/src/config.c +++ b/src/config.c @@ -2505,6 +2505,19 @@ static int updateReplBacklogSize(const char **err) { return 1; } +static int updateMaxmemoryReserved(const char **err) { + UNUSED(err); + if (server.maxmemory_reserved_scale) { + if (server.maxmemory_reserved_scale < 10) { + server.maxmemory_reserved_scale = 10; + } else if (server.maxmemory_reserved_scale > 60) { + server.maxmemory_reserved_scale = 60; + } + } + server.maxmemory_available = (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); + return 1; +} + static int updateMaxmemory(const char **err) { UNUSED(err); if (server.maxmemory) { @@ -2516,6 +2529,8 @@ static int updateMaxmemory(const char **err) { "depending on the maxmemory-policy.", server.maxmemory, used); } + server.maxmemory_available = + (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); startEvictionTimeProc(); } return 1; @@ -3216,6 +3231,7 @@ standardConfig static_configs[] = { createIntConfig("lfu-decay-time", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_decay_time, 1, INTEGER_CONFIG, NULL, NULL), createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, server.replica_priority, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL), + createIntConfig("maxmemory-reserved-scale", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_reserved_scale, 0, INTEGER_CONFIG, NULL, updateMaxmemoryReserved), createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, 64, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL), createIntConfig("maxmemory-eviction-tenacity", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */ diff --git a/src/evict.c b/src/evict.c index 5208328b32..d5d5765b5c 100644 --- a/src/evict.c +++ b/src/evict.c @@ -402,7 +402,11 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev if (level) *level = 0; return C_OK; } - if (mem_reported <= server.maxmemory && !level) return C_OK; + + if (server.maxmemory_reserved_scale) { + if (mem_reported <= server.maxmemory_available && !level) return C_OK; + } else if (mem_reported <= server.maxmemory && !level) + return C_OK; /* Remove the size of replicas output buffers and AOF buffer from the * count of used memory. */ @@ -411,15 +415,29 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev mem_used = (mem_used > overhead) ? mem_used - overhead : 0; /* Compute the ratio of memory usage. */ - if (level) *level = (float)mem_used / (float)server.maxmemory; + if (level) { + if (server.maxmemory_reserved_scale) + *level = (float)mem_used / (float)server.maxmemory_available; + else + *level = (float)mem_used / (float)server.maxmemory; + } - if (mem_reported <= server.maxmemory) return C_OK; + if (server.maxmemory_reserved_scale) { + if (mem_reported <= server.maxmemory_available) return C_OK; + } else if (mem_reported <= server.maxmemory) + return C_OK; /* Check if we are still over the memory limit. */ - if (mem_used <= server.maxmemory) return C_OK; + if (server.maxmemory_reserved_scale) { + if (mem_used <= server.maxmemory_available) return C_OK; + } else if (mem_used <= server.maxmemory) + return C_OK; /* Compute how much memory we need to free. */ - mem_tofree = mem_used - server.maxmemory; + if (server.maxmemory_reserved_scale) { + mem_tofree = mem_used - server.maxmemory_available; + } else + mem_tofree = mem_used - server.maxmemory; if (logical) *logical = mem_used; if (tofree) *tofree = mem_tofree; diff --git a/src/server.c b/src/server.c index aebbb57a93..dacc647090 100644 --- a/src/server.c +++ b/src/server.c @@ -2698,6 +2698,11 @@ void initServer(void) { server.client_mem_usage_buckets = NULL; resetReplicationBuffer(); + if (server.maxmemory) { + server.maxmemory_available = + (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); + } + /* Make sure the locale is set on startup based on the config file. */ if (setlocale(LC_COLLATE, server.locale_collate) == NULL) { serverLog(LL_WARNING, "Failed to configure LOCALE for invalid locale name."); @@ -5623,6 +5628,7 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { char used_memory_scripts_hmem[64]; char used_memory_rss_hmem[64]; char maxmemory_hmem[64]; + char maxmemory_available_hmem[64]; size_t zmalloc_used = zmalloc_used_memory(); size_t total_system_mem = server.system_memory_size; const char *evict_policy = evictPolicyToString(); @@ -5644,6 +5650,7 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { bytesToHuman(used_memory_scripts_hmem, sizeof(used_memory_scripts_hmem), mh->lua_caches + mh->functions_caches); bytesToHuman(used_memory_rss_hmem, sizeof(used_memory_rss_hmem), server.cron_malloc_stats.process_rss); bytesToHuman(maxmemory_hmem, sizeof(maxmemory_hmem), server.maxmemory); + bytesToHuman(maxmemory_available_hmem, sizeof(maxmemory_available_hmem), server.maxmemory_available); if (sections++) info = sdscat(info, "\r\n"); info = sdscatprintf( @@ -5682,6 +5689,9 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { "maxmemory:%lld\r\n", server.maxmemory, "maxmemory_human:%s\r\n", maxmemory_hmem, "maxmemory_policy:%s\r\n", evict_policy, + "maxmemory_reserved_scale:%d\r\n",server.maxmemory_reserved_scale, + "maxmemory_available:%lld\r\n",server.maxmemory_available, + "maxmemory_available_human:%s\r\n",maxmemory_available_hmem, "allocator_frag_ratio:%.2f\r\n", mh->allocator_frag, "allocator_frag_bytes:%zu\r\n", mh->allocator_frag_bytes, "allocator_rss_ratio:%.2f\r\n", mh->allocator_rss, diff --git a/src/server.h b/src/server.h index 531ca8e7c8..aa5543cc2a 100644 --- a/src/server.h +++ b/src/server.h @@ -2101,6 +2101,8 @@ struct valkeyServer { ssize_t maxmemory_clients; /* Memory limit for total client buffers */ int maxmemory_policy; /* Policy for key eviction */ int maxmemory_samples; /* Precision of random sampling */ + int maxmemory_reserved_scale; + unsigned long long maxmemory_available; int maxmemory_eviction_tenacity; /* Aggressiveness of eviction processing */ int lfu_log_factor; /* LFU logarithmic counter factor. */ int lfu_decay_time; /* LFU counter decay factor. */ diff --git a/valkey.conf b/valkey.conf index bf82b01874..467ba099d8 100644 --- a/valkey.conf +++ b/valkey.conf @@ -1272,6 +1272,13 @@ acllog-max-len 128 # in the system. It's a tradeoff between memory, CPU and latency. # # active-expire-effort 1 +# +# It allows the valkey to evict keys earlier. The value of this parameter represents +# percent of the maxmemory value. It means how much memory the valkey instance want to hold +# not to store the data. +# Default is 0, and the value could be set between 10 to 60. +# +# maxmemory-reserved-scale 0 ############################# LAZY FREEING #################################### From 2709d1a799c8bbb825775e7f858697d79df1c555 Mon Sep 17 00:00:00 2001 From: hwware Date: Fri, 26 Jul 2024 15:31:50 +0000 Subject: [PATCH 02/12] clang-format adjust Signed-off-by: hwware --- src/server.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server.h b/src/server.h index aa5543cc2a..7a893651df 100644 --- a/src/server.h +++ b/src/server.h @@ -2101,8 +2101,8 @@ struct valkeyServer { ssize_t maxmemory_clients; /* Memory limit for total client buffers */ int maxmemory_policy; /* Policy for key eviction */ int maxmemory_samples; /* Precision of random sampling */ - int maxmemory_reserved_scale; - unsigned long long maxmemory_available; + int maxmemory_reserved_scale; /* Percent of the maxmemory value */ + unsigned long long maxmemory_available; /* Max memory to store data */ int maxmemory_eviction_tenacity; /* Aggressiveness of eviction processing */ int lfu_log_factor; /* LFU logarithmic counter factor. */ int lfu_decay_time; /* LFU counter decay factor. */ From 777f057b246735549c2e2a2585c778ec04e57eb0 Mon Sep 17 00:00:00 2001 From: hwware Date: Thu, 26 Sep 2024 02:44:36 +0000 Subject: [PATCH 03/12] Rebase and address Ping commnt Signed-off-by: hwware --- src/config.c | 5 ++--- src/evict.c | 23 ++++++----------------- src/server.c | 3 +-- src/server.h | 3 +++ 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/config.c b/src/config.c index aa3e7db9d0..8bb6c901c8 100644 --- a/src/config.c +++ b/src/config.c @@ -2514,7 +2514,7 @@ static int updateMaxmemoryReserved(const char **err) { server.maxmemory_reserved_scale = 60; } } - server.maxmemory_available = (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); + calculateMaxAvailableMemory(); return 1; } @@ -2529,8 +2529,7 @@ static int updateMaxmemory(const char **err) { "depending on the maxmemory-policy.", server.maxmemory, used); } - server.maxmemory_available = - (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); + calculateMaxAvailableMemory(); startEvictionTimeProc(); } return 1; diff --git a/src/evict.c b/src/evict.c index d5d5765b5c..3836b64ea7 100644 --- a/src/evict.c +++ b/src/evict.c @@ -405,8 +405,9 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev if (server.maxmemory_reserved_scale) { if (mem_reported <= server.maxmemory_available && !level) return C_OK; - } else if (mem_reported <= server.maxmemory && !level) + } else if (mem_reported <= server.maxmemory && !level) { return C_OK; + } /* Remove the size of replicas output buffers and AOF buffer from the * count of used memory. */ @@ -416,28 +417,16 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev /* Compute the ratio of memory usage. */ if (level) { - if (server.maxmemory_reserved_scale) - *level = (float)mem_used / (float)server.maxmemory_available; - else - *level = (float)mem_used / (float)server.maxmemory; + *level = (float)mem_used / (float)server.maxmemory_available; } - if (server.maxmemory_reserved_scale) { - if (mem_reported <= server.maxmemory_available) return C_OK; - } else if (mem_reported <= server.maxmemory) - return C_OK; + if (mem_reported <= server.maxmemory_available) return C_OK; /* Check if we are still over the memory limit. */ - if (server.maxmemory_reserved_scale) { - if (mem_used <= server.maxmemory_available) return C_OK; - } else if (mem_used <= server.maxmemory) - return C_OK; + if (mem_used <= server.maxmemory_available) return C_OK; /* Compute how much memory we need to free. */ - if (server.maxmemory_reserved_scale) { - mem_tofree = mem_used - server.maxmemory_available; - } else - mem_tofree = mem_used - server.maxmemory; + mem_tofree = mem_used - server.maxmemory_available; if (logical) *logical = mem_used; if (tofree) *tofree = mem_tofree; diff --git a/src/server.c b/src/server.c index dacc647090..742fda4e35 100644 --- a/src/server.c +++ b/src/server.c @@ -2699,8 +2699,7 @@ void initServer(void) { resetReplicationBuffer(); if (server.maxmemory) { - server.maxmemory_available = - (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); + calculateMaxAvailableMemory(); } /* Make sure the locale is set on startup based on the config file. */ diff --git a/src/server.h b/src/server.h index 7a893651df..040b771c67 100644 --- a/src/server.h +++ b/src/server.h @@ -3037,6 +3037,9 @@ void trimStringObjectIfNeeded(robj *o, int trim_small_values); static inline int canUseSharedObject(void) { return server.maxmemory == 0 || !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS); } +static inline void calculateMaxAvailableMemory(void) { + server.maxmemory_available = (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); +} #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR) /* Synchronous I/O with timeout */ From d06ad858555bae4805b208a5212132968eb25e71 Mon Sep 17 00:00:00 2001 From: hwware Date: Thu, 3 Oct 2024 12:32:53 +0000 Subject: [PATCH 04/12] adjust clang-format Signed-off-by: hwware --- src/config.c | 2 +- src/evict.c | 2 +- src/server.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config.c b/src/config.c index 8bb6c901c8..c79c08d583 100644 --- a/src/config.c +++ b/src/config.c @@ -2529,7 +2529,7 @@ static int updateMaxmemory(const char **err) { "depending on the maxmemory-policy.", server.maxmemory, used); } - calculateMaxAvailableMemory(); + calculateMaxAvailableMemory(); startEvictionTimeProc(); } return 1; diff --git a/src/evict.c b/src/evict.c index 3836b64ea7..47cf9dab2e 100644 --- a/src/evict.c +++ b/src/evict.c @@ -417,7 +417,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev /* Compute the ratio of memory usage. */ if (level) { - *level = (float)mem_used / (float)server.maxmemory_available; + *level = (float)mem_used / (float)server.maxmemory_available; } if (mem_reported <= server.maxmemory_available) return C_OK; diff --git a/src/server.c b/src/server.c index 742fda4e35..e9c9108cb2 100644 --- a/src/server.c +++ b/src/server.c @@ -5688,9 +5688,9 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { "maxmemory:%lld\r\n", server.maxmemory, "maxmemory_human:%s\r\n", maxmemory_hmem, "maxmemory_policy:%s\r\n", evict_policy, - "maxmemory_reserved_scale:%d\r\n",server.maxmemory_reserved_scale, - "maxmemory_available:%lld\r\n",server.maxmemory_available, - "maxmemory_available_human:%s\r\n",maxmemory_available_hmem, + "maxmemory_reserved_scale:%d\r\n", server.maxmemory_reserved_scale, + "maxmemory_available:%lld\r\n", server.maxmemory_available, + "maxmemory_available_human:%s\r\n", maxmemory_available_hmem, "allocator_frag_ratio:%.2f\r\n", mh->allocator_frag, "allocator_frag_bytes:%zu\r\n", mh->allocator_frag_bytes, "allocator_rss_ratio:%.2f\r\n", mh->allocator_rss, From bf1f3d219f269a1fe160f9e52e91e051c933d7fb Mon Sep 17 00:00:00 2001 From: hwware Date: Mon, 7 Oct 2024 09:29:56 +0000 Subject: [PATCH 05/12] Add test case and Address Ping Comment Signed-off-by: hwware --- src/config.c | 4 ++-- src/evict.c | 8 ++------ src/server.c | 2 +- src/server.h | 2 +- tests/unit/maxmemory.tcl | 19 +++++++++++++++++++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/config.c b/src/config.c index c79c08d583..404b191a83 100644 --- a/src/config.c +++ b/src/config.c @@ -2514,7 +2514,7 @@ static int updateMaxmemoryReserved(const char **err) { server.maxmemory_reserved_scale = 60; } } - calculateMaxAvailableMemory(); + updateMaxAvailableMemory(); return 1; } @@ -2529,7 +2529,7 @@ static int updateMaxmemory(const char **err) { "depending on the maxmemory-policy.", server.maxmemory, used); } - calculateMaxAvailableMemory(); + updateMaxAvailableMemory(); startEvictionTimeProc(); } return 1; diff --git a/src/evict.c b/src/evict.c index 47cf9dab2e..9655b76b9b 100644 --- a/src/evict.c +++ b/src/evict.c @@ -398,16 +398,12 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev if (total) *total = mem_reported; /* We may return ASAP if there is no need to compute the level. */ - if (!server.maxmemory) { + if (!server.maxmemory_available) { if (level) *level = 0; return C_OK; } - if (server.maxmemory_reserved_scale) { - if (mem_reported <= server.maxmemory_available && !level) return C_OK; - } else if (mem_reported <= server.maxmemory && !level) { - return C_OK; - } + if (mem_reported <= server.maxmemory_available && !level) return C_OK; /* Remove the size of replicas output buffers and AOF buffer from the * count of used memory. */ diff --git a/src/server.c b/src/server.c index e9c9108cb2..c93af7c3b9 100644 --- a/src/server.c +++ b/src/server.c @@ -2699,7 +2699,7 @@ void initServer(void) { resetReplicationBuffer(); if (server.maxmemory) { - calculateMaxAvailableMemory(); + updateMaxAvailableMemory(); } /* Make sure the locale is set on startup based on the config file. */ diff --git a/src/server.h b/src/server.h index 040b771c67..c4b6da0c93 100644 --- a/src/server.h +++ b/src/server.h @@ -3037,7 +3037,7 @@ void trimStringObjectIfNeeded(robj *o, int trim_small_values); static inline int canUseSharedObject(void) { return server.maxmemory == 0 || !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS); } -static inline void calculateMaxAvailableMemory(void) { +static inline void updateMaxAvailableMemory(void) { server.maxmemory_available = (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); } #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR) diff --git a/tests/unit/maxmemory.tcl b/tests/unit/maxmemory.tcl index 89e9699a3e..33a3fd70ff 100644 --- a/tests/unit/maxmemory.tcl +++ b/tests/unit/maxmemory.tcl @@ -145,6 +145,25 @@ start_server {tags {"maxmemory" "external:skip"}} { } start_server {tags {"maxmemory external:skip"}} { + + test "maxmemory-reserved-scale" { + r flushdb + r config set maxmemory 10mb + set current_dbsize 0 + + foreach scale_value {30 10 0} { + r config set maxmemory-reserved-scale $scale_value + # fill 20mb using 200 keys of 100kb + catch { for {set j 0} {$j < 200} {incr j} { + r setrange $j 100000 x + }} e + assert_match {*OOM*} $e + assert_lessthan $current_dbsize [r dbsize] + set current_dbsize [r dbsize] + } + r flushdb + } + test "Without maxmemory small integers are shared" { r config set maxmemory 0 r set a 1 From ac961b2c1feb45b408686e27196e356aaabd8ca0 Mon Sep 17 00:00:00 2001 From: hwware Date: Fri, 18 Oct 2024 05:51:10 +0000 Subject: [PATCH 06/12] Update comments Signed-off-by: hwware --- src/config.c | 2 ++ valkey.conf | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/config.c b/src/config.c index 404b191a83..3242591f82 100644 --- a/src/config.c +++ b/src/config.c @@ -2505,6 +2505,8 @@ static int updateReplBacklogSize(const char **err) { return 1; } +/* Adjusts `maxmemory_reserved_scale` to ensure it remains within the valid range of 10 to 60, if set. + * Once adjusted, the available memory is recalculated to reflect the new reserved memory. */ static int updateMaxmemoryReserved(const char **err) { UNUSED(err); if (server.maxmemory_reserved_scale) { diff --git a/valkey.conf b/valkey.conf index 467ba099d8..384e1d89b0 100644 --- a/valkey.conf +++ b/valkey.conf @@ -1273,10 +1273,10 @@ acllog-max-len 128 # # active-expire-effort 1 # -# It allows the valkey to evict keys earlier. The value of this parameter represents -# percent of the maxmemory value. It means how much memory the valkey instance want to hold -# not to store the data. -# Default is 0, and the value could be set between 10 to 60. +# This parameter allows Valkey to evict keys earlier. The value represents +# a percentage of the maxmemory limit, indicating how much memory the Valkey instance reserves +# without storing data. +# The default is 0, and the value can be set between 10 and 60. # # maxmemory-reserved-scale 0 From 1169453c0ac317e7a4515a2eb662bc9d5faf8346 Mon Sep 17 00:00:00 2001 From: hwware Date: Mon, 28 Oct 2024 13:41:10 +0000 Subject: [PATCH 07/12] Update level but test case fail Signed-off-by: hwware --- src/evict.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/evict.c b/src/evict.c index 9655b76b9b..2219244f94 100644 --- a/src/evict.c +++ b/src/evict.c @@ -413,7 +413,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev /* Compute the ratio of memory usage. */ if (level) { - *level = (float)mem_used / (float)server.maxmemory_available; + *level = (float)mem_used / (float)server.maxmemory; } if (mem_reported <= server.maxmemory_available) return C_OK; @@ -729,8 +729,18 @@ int performEvictions(void) { } } /* at this point, the memory is OK, or we have reached the time limit */ + if (server.maxmemory_available) { + size_t mem_used = zmalloc_used_memory(); + size_t overhead = freeMemoryGetNotCountedMemory(); + mem_used = (mem_used > overhead) ? mem_used - overhead : 0; + if (mem_used > server.maxmemory) { + result = EVICT_FAIL; + goto cant_free; + } + } result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK; + cant_free: if (result == EVICT_FAIL) { /* At this point, we have run out of evictable items. It's possible From 317319303b286f87e681e739415b623bd8e327f1 Mon Sep 17 00:00:00 2001 From: Shivshankar-Reddy Date: Tue, 29 Oct 2024 01:16:07 +0000 Subject: [PATCH 08/12] fix for the test failure Signed-off-by: Shivshankar-Reddy Signed-off-by: hwware --- src/evict.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evict.c b/src/evict.c index 2219244f94..51746a7269 100644 --- a/src/evict.c +++ b/src/evict.c @@ -729,7 +729,7 @@ int performEvictions(void) { } } /* at this point, the memory is OK, or we have reached the time limit */ - if (server.maxmemory_available) { + if (server.maxmemory_reserved_scale && server.maxmemory_available) { size_t mem_used = zmalloc_used_memory(); size_t overhead = freeMemoryGetNotCountedMemory(); mem_used = (mem_used > overhead) ? mem_used - overhead : 0; From 3f6e99b6ba79971cac27a812397c330283ef5a34 Mon Sep 17 00:00:00 2001 From: hwware Date: Sun, 3 Nov 2024 08:23:28 +0000 Subject: [PATCH 09/12] Update to maxmemory_soft_scale Signed-off-by: hwware --- src/config.c | 22 +++++++++++----------- src/evict.c | 13 ++++++------- src/server.c | 12 ++++++------ src/server.h | 8 ++++---- tests/unit/maxmemory.tcl | 4 ++-- valkey.conf | 14 +++++++------- 6 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/config.c b/src/config.c index 3242591f82..c9efa7f1ba 100644 --- a/src/config.c +++ b/src/config.c @@ -2505,18 +2505,18 @@ static int updateReplBacklogSize(const char **err) { return 1; } -/* Adjusts `maxmemory_reserved_scale` to ensure it remains within the valid range of 10 to 60, if set. - * Once adjusted, the available memory is recalculated to reflect the new reserved memory. */ -static int updateMaxmemoryReserved(const char **err) { +/* Adjusts `maxmemory_soft_scale` to ensure it remains within the valid range of 10 to 60, if set. + * Once adjusted, the available memory is recalculated to reflect the new soft maxmemory. */ +static int updateMaxmemorySoftScale(const char **err) { UNUSED(err); - if (server.maxmemory_reserved_scale) { - if (server.maxmemory_reserved_scale < 10) { - server.maxmemory_reserved_scale = 10; - } else if (server.maxmemory_reserved_scale > 60) { - server.maxmemory_reserved_scale = 60; + if (server.maxmemory_soft_scale) { + if (server.maxmemory_soft_scale < 10) { + server.maxmemory_soft_scale = 10; + } else if (server.maxmemory_soft_scale > 60) { + server.maxmemory_soft_scale = 60; } } - updateMaxAvailableMemory(); + updateSoftMaxmemoryValue(); return 1; } @@ -2531,7 +2531,7 @@ static int updateMaxmemory(const char **err) { "depending on the maxmemory-policy.", server.maxmemory, used); } - updateMaxAvailableMemory(); + updateSoftMaxmemoryValue(); startEvictionTimeProc(); } return 1; @@ -3232,7 +3232,7 @@ standardConfig static_configs[] = { createIntConfig("lfu-decay-time", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_decay_time, 1, INTEGER_CONFIG, NULL, NULL), createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, server.replica_priority, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL), - createIntConfig("maxmemory-reserved-scale", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_reserved_scale, 0, INTEGER_CONFIG, NULL, updateMaxmemoryReserved), + createIntConfig("maxmemory-soft-scale", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_soft_scale, 0, INTEGER_CONFIG, NULL, updateMaxmemorySoftScale), createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, 64, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL), createIntConfig("maxmemory-eviction-tenacity", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */ diff --git a/src/evict.c b/src/evict.c index 51746a7269..590236195d 100644 --- a/src/evict.c +++ b/src/evict.c @@ -398,12 +398,12 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev if (total) *total = mem_reported; /* We may return ASAP if there is no need to compute the level. */ - if (!server.maxmemory_available) { + if (!server.maxmemory_soft) { if (level) *level = 0; return C_OK; } - if (mem_reported <= server.maxmemory_available && !level) return C_OK; + if (mem_reported <= server.maxmemory_soft && !level) return C_OK; /* Remove the size of replicas output buffers and AOF buffer from the * count of used memory. */ @@ -416,13 +416,13 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev *level = (float)mem_used / (float)server.maxmemory; } - if (mem_reported <= server.maxmemory_available) return C_OK; + if (mem_reported <= server.maxmemory_soft) return C_OK; /* Check if we are still over the memory limit. */ - if (mem_used <= server.maxmemory_available) return C_OK; + if (mem_used <= server.maxmemory_soft) return C_OK; /* Compute how much memory we need to free. */ - mem_tofree = mem_used - server.maxmemory_available; + mem_tofree = mem_used - server.maxmemory_soft; if (logical) *logical = mem_used; if (tofree) *tofree = mem_tofree; @@ -729,7 +729,7 @@ int performEvictions(void) { } } /* at this point, the memory is OK, or we have reached the time limit */ - if (server.maxmemory_reserved_scale && server.maxmemory_available) { + if (server.maxmemory_soft_scale) { size_t mem_used = zmalloc_used_memory(); size_t overhead = freeMemoryGetNotCountedMemory(); mem_used = (mem_used > overhead) ? mem_used - overhead : 0; @@ -740,7 +740,6 @@ int performEvictions(void) { } result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK; - cant_free: if (result == EVICT_FAIL) { /* At this point, we have run out of evictable items. It's possible diff --git a/src/server.c b/src/server.c index c93af7c3b9..a664bc714b 100644 --- a/src/server.c +++ b/src/server.c @@ -2699,7 +2699,7 @@ void initServer(void) { resetReplicationBuffer(); if (server.maxmemory) { - updateMaxAvailableMemory(); + updateSoftMaxmemoryValue(); } /* Make sure the locale is set on startup based on the config file. */ @@ -5627,7 +5627,7 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { char used_memory_scripts_hmem[64]; char used_memory_rss_hmem[64]; char maxmemory_hmem[64]; - char maxmemory_available_hmem[64]; + char maxmemory_soft_hmem[64]; size_t zmalloc_used = zmalloc_used_memory(); size_t total_system_mem = server.system_memory_size; const char *evict_policy = evictPolicyToString(); @@ -5649,7 +5649,7 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { bytesToHuman(used_memory_scripts_hmem, sizeof(used_memory_scripts_hmem), mh->lua_caches + mh->functions_caches); bytesToHuman(used_memory_rss_hmem, sizeof(used_memory_rss_hmem), server.cron_malloc_stats.process_rss); bytesToHuman(maxmemory_hmem, sizeof(maxmemory_hmem), server.maxmemory); - bytesToHuman(maxmemory_available_hmem, sizeof(maxmemory_available_hmem), server.maxmemory_available); + bytesToHuman(maxmemory_soft_hmem, sizeof(maxmemory_soft_hmem), server.maxmemory_soft); if (sections++) info = sdscat(info, "\r\n"); info = sdscatprintf( @@ -5688,9 +5688,9 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) { "maxmemory:%lld\r\n", server.maxmemory, "maxmemory_human:%s\r\n", maxmemory_hmem, "maxmemory_policy:%s\r\n", evict_policy, - "maxmemory_reserved_scale:%d\r\n", server.maxmemory_reserved_scale, - "maxmemory_available:%lld\r\n", server.maxmemory_available, - "maxmemory_available_human:%s\r\n", maxmemory_available_hmem, + "maxmemory_soft_scale:%d\r\n", server.maxmemory_soft_scale, + "maxmemory_soft:%lld\r\n", server.maxmemory_soft, + "maxmemory_soft_human:%s\r\n", maxmemory_soft_hmem, "allocator_frag_ratio:%.2f\r\n", mh->allocator_frag, "allocator_frag_bytes:%zu\r\n", mh->allocator_frag_bytes, "allocator_rss_ratio:%.2f\r\n", mh->allocator_rss, diff --git a/src/server.h b/src/server.h index c4b6da0c93..3c85912445 100644 --- a/src/server.h +++ b/src/server.h @@ -2101,8 +2101,8 @@ struct valkeyServer { ssize_t maxmemory_clients; /* Memory limit for total client buffers */ int maxmemory_policy; /* Policy for key eviction */ int maxmemory_samples; /* Precision of random sampling */ - int maxmemory_reserved_scale; /* Percent of the maxmemory value */ - unsigned long long maxmemory_available; /* Max memory to store data */ + int maxmemory_soft_scale; /* Percent of the soft maxmemory value */ + unsigned long long maxmemory_soft; /* Soft maxmemory value to store data */ int maxmemory_eviction_tenacity; /* Aggressiveness of eviction processing */ int lfu_log_factor; /* LFU logarithmic counter factor. */ int lfu_decay_time; /* LFU counter decay factor. */ @@ -3037,8 +3037,8 @@ void trimStringObjectIfNeeded(robj *o, int trim_small_values); static inline int canUseSharedObject(void) { return server.maxmemory == 0 || !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS); } -static inline void updateMaxAvailableMemory(void) { - server.maxmemory_available = (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_reserved_scale); +static inline void updateSoftMaxmemoryValue(void) { + server.maxmemory_soft = (unsigned long long)server.maxmemory / 100.0 * (100 - server.maxmemory_soft_scale); } #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR) diff --git a/tests/unit/maxmemory.tcl b/tests/unit/maxmemory.tcl index 33a3fd70ff..d71b23ce47 100644 --- a/tests/unit/maxmemory.tcl +++ b/tests/unit/maxmemory.tcl @@ -146,13 +146,13 @@ start_server {tags {"maxmemory" "external:skip"}} { start_server {tags {"maxmemory external:skip"}} { - test "maxmemory-reserved-scale" { + test "maxmemory-soft-scale" { r flushdb r config set maxmemory 10mb set current_dbsize 0 foreach scale_value {30 10 0} { - r config set maxmemory-reserved-scale $scale_value + r config set maxmemory-soft-scale $scale_value # fill 20mb using 200 keys of 100kb catch { for {set j 0} {$j < 200} {incr j} { r setrange $j 100000 x diff --git a/valkey.conf b/valkey.conf index 384e1d89b0..3db56f99aa 100644 --- a/valkey.conf +++ b/valkey.conf @@ -1216,6 +1216,13 @@ acllog-max-len 128 # # maxmemory-policy noeviction +# `maxmemory-soft-scale` defines a "soft" maxmemory threshold as a scale of the `maxmemory` limit. +# When memory usage exceeds this scale, Valkey begins proactive key eviction. However, exceeding this +# threshold does not immediately reject new write commands; only the hard `maxmemory` limit will do so. +# This scale is specified as a percentage of `maxmemory`, with a valid range between 10 and 60. +# +# maxmemory-soft-scale 0 + # LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated # algorithms (in order to save memory), so you can tune it for speed or # accuracy. By default the server will check five keys and pick the one that was @@ -1272,13 +1279,6 @@ acllog-max-len 128 # in the system. It's a tradeoff between memory, CPU and latency. # # active-expire-effort 1 -# -# This parameter allows Valkey to evict keys earlier. The value represents -# a percentage of the maxmemory limit, indicating how much memory the Valkey instance reserves -# without storing data. -# The default is 0, and the value can be set between 10 and 60. -# -# maxmemory-reserved-scale 0 ############################# LAZY FREEING #################################### From bc908e7316d90d72b8e9140c413d9cf73b7d5f98 Mon Sep 17 00:00:00 2001 From: hwware Date: Wed, 20 Nov 2024 21:09:34 +0000 Subject: [PATCH 10/12] Update eviction logic Signed-off-by: hwware --- src/evict.c | 32 +++++++++++++++++++------------- src/module.c | 6 +++--- src/server.h | 2 +- tests/unit/maxmemory.tcl | 20 +------------------- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/evict.c b/src/evict.c index 590236195d..05a49c7ff0 100644 --- a/src/evict.c +++ b/src/evict.c @@ -389,7 +389,7 @@ size_t freeMemoryGetNotCountedMemory(void) { * limit. * (Populated both for C_ERR and C_OK) */ -int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level) { +int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level, unsigned long long checked_maxmemory) { size_t mem_reported, mem_used, mem_tofree; /* Check if we are over the memory usage limit. If we are not, no need @@ -398,12 +398,12 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev if (total) *total = mem_reported; /* We may return ASAP if there is no need to compute the level. */ - if (!server.maxmemory_soft) { + if (!checked_maxmemory) { if (level) *level = 0; return C_OK; } - if (mem_reported <= server.maxmemory_soft && !level) return C_OK; + if (mem_reported <= checked_maxmemory && !level) return C_OK; /* Remove the size of replicas output buffers and AOF buffer from the * count of used memory. */ @@ -416,10 +416,12 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev *level = (float)mem_used / (float)server.maxmemory; } - if (mem_reported <= server.maxmemory_soft) return C_OK; + if (mem_reported <= checked_maxmemory) return C_OK; /* Check if we are still over the memory limit. */ - if (mem_used <= server.maxmemory_soft) return C_OK; + /* if checked_maxmemory is equal to maxmemory and mem_used > checked_maxmemory then OOM */ + /* if checked_maxmemory is equal to maxmemory_soft and mem_used > checked_maxmemory then there is no OOM but eviction happens */ + if (mem_used <= checked_maxmemory) return C_OK; /* Compute how much memory we need to free. */ mem_tofree = mem_used - server.maxmemory_soft; @@ -544,7 +546,7 @@ int performEvictions(void) { int replicas = listLength(server.replicas); int result = EVICT_FAIL; - if (getMaxmemoryState(&mem_reported, NULL, &mem_tofree, NULL) == C_OK) { + if (getMaxmemoryState(&mem_reported, NULL, &mem_tofree, NULL, server.maxmemory) == C_OK) { result = EVICT_OK; goto update_metrics; } @@ -710,7 +712,7 @@ int performEvictions(void) { * across the dbAsyncDelete() call, while the thread can * release the memory all the time. */ if (server.lazyfree_lazy_eviction) { - if (getMaxmemoryState(NULL, NULL, NULL, NULL) == C_OK) { + if (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory) == C_OK) { break; } } @@ -725,19 +727,23 @@ int performEvictions(void) { } } } else { - goto cant_free; /* nothing to free... */ + if (server.maxmemory_soft_scale) { + break; + } else { + goto cant_free; /* nothing to free... */ + } } } - /* at this point, the memory is OK, or we have reached the time limit */ if (server.maxmemory_soft_scale) { size_t mem_used = zmalloc_used_memory(); size_t overhead = freeMemoryGetNotCountedMemory(); mem_used = (mem_used > overhead) ? mem_used - overhead : 0; - if (mem_used > server.maxmemory) { - result = EVICT_FAIL; - goto cant_free; + if (mem_used < server.maxmemory) { + result = EVICT_OK; + goto update_metrics; } } + /* at this point, the memory is OK, or we have reached the time limit */ result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK; cant_free: @@ -748,7 +754,7 @@ int performEvictions(void) { mstime_t lazyfree_latency; latencyStartMonitor(lazyfree_latency); while (bioPendingJobsOfType(BIO_LAZY_FREE) && elapsedUs(evictionTimer) < eviction_time_limit_us) { - if (getMaxmemoryState(NULL, NULL, NULL, NULL) == C_OK) { + if (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory) == C_OK) { result = EVICT_OK; break; } diff --git a/src/module.c b/src/module.c index 1e98b36f30..ae484a5542 100644 --- a/src/module.c +++ b/src/module.c @@ -3913,7 +3913,7 @@ int VM_GetContextFlags(ValkeyModuleCtx *ctx) { /* OOM flag. */ float level; - int retval = getMaxmemoryState(NULL, NULL, NULL, &level); + int retval = getMaxmemoryState(NULL, NULL, NULL, &level, server.maxmemory); if (retval == C_ERR) flags |= VALKEYMODULE_CTX_FLAGS_OOM; if (level > 0.75) flags |= VALKEYMODULE_CTX_FLAGS_OOM_WARNING; @@ -6310,7 +6310,7 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const /* On background thread we can not count on server.pre_command_oom_state. * Because it is only set on the main thread, in such case we will check * the actual memory usage. */ - oom_state = (getMaxmemoryState(NULL, NULL, NULL, NULL) == C_ERR); + oom_state = (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory) == C_ERR); } else { oom_state = server.pre_command_oom_state; } @@ -10855,7 +10855,7 @@ size_t VM_MallocSizeDict(ValkeyModuleDict *dict) { */ float VM_GetUsedMemoryRatio(void) { float level; - getMaxmemoryState(NULL, NULL, NULL, &level); + getMaxmemoryState(NULL, NULL, NULL, &level, server.maxmemory); return level; } diff --git a/src/server.h b/src/server.h index 3c85912445..d42206dd9c 100644 --- a/src/server.h +++ b/src/server.h @@ -3282,7 +3282,7 @@ int zslLexValueGteMin(sds value, zlexrangespec *spec); int zslLexValueLteMax(sds value, zlexrangespec *spec); /* Core functions */ -int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level); +int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level, unsigned long long checked_maxmemory); size_t freeMemoryGetNotCountedMemory(void); int overMaxmemoryAfterAlloc(size_t moremem); uint64_t getCommandFlags(client *c); diff --git a/tests/unit/maxmemory.tcl b/tests/unit/maxmemory.tcl index d71b23ce47..f4b3ece038 100644 --- a/tests/unit/maxmemory.tcl +++ b/tests/unit/maxmemory.tcl @@ -146,24 +146,6 @@ start_server {tags {"maxmemory" "external:skip"}} { start_server {tags {"maxmemory external:skip"}} { - test "maxmemory-soft-scale" { - r flushdb - r config set maxmemory 10mb - set current_dbsize 0 - - foreach scale_value {30 10 0} { - r config set maxmemory-soft-scale $scale_value - # fill 20mb using 200 keys of 100kb - catch { for {set j 0} {$j < 200} {incr j} { - r setrange $j 100000 x - }} e - assert_match {*OOM*} $e - assert_lessthan $current_dbsize [r dbsize] - set current_dbsize [r dbsize] - } - r flushdb - } - test "Without maxmemory small integers are shared" { r config set maxmemory 0 r set a 1 @@ -647,4 +629,4 @@ start_server {tags {"maxmemory" "external:skip"}} { assert_equal [r dbsize] {0} } -} \ No newline at end of file +} From 3e5b5079f49123d5cad16e6af23639fc4a750664 Mon Sep 17 00:00:00 2001 From: hwware Date: Thu, 21 Nov 2024 15:47:01 +0000 Subject: [PATCH 11/12] update passed parameter Signed-off-by: hwware --- src/evict.c | 6 +++--- tests/unit/maxmemory.tcl | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/evict.c b/src/evict.c index 05a49c7ff0..0ada8573f7 100644 --- a/src/evict.c +++ b/src/evict.c @@ -546,7 +546,7 @@ int performEvictions(void) { int replicas = listLength(server.replicas); int result = EVICT_FAIL; - if (getMaxmemoryState(&mem_reported, NULL, &mem_tofree, NULL, server.maxmemory) == C_OK) { + if (getMaxmemoryState(&mem_reported, NULL, &mem_tofree, NULL, server.maxmemory_soft) == C_OK) { result = EVICT_OK; goto update_metrics; } @@ -712,7 +712,7 @@ int performEvictions(void) { * across the dbAsyncDelete() call, while the thread can * release the memory all the time. */ if (server.lazyfree_lazy_eviction) { - if (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory) == C_OK) { + if (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory_soft) == C_OK) { break; } } @@ -754,7 +754,7 @@ int performEvictions(void) { mstime_t lazyfree_latency; latencyStartMonitor(lazyfree_latency); while (bioPendingJobsOfType(BIO_LAZY_FREE) && elapsedUs(evictionTimer) < eviction_time_limit_us) { - if (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory) == C_OK) { + if (getMaxmemoryState(NULL, NULL, NULL, NULL, server.maxmemory_soft) == C_OK) { result = EVICT_OK; break; } diff --git a/tests/unit/maxmemory.tcl b/tests/unit/maxmemory.tcl index f4b3ece038..5266fc5280 100644 --- a/tests/unit/maxmemory.tcl +++ b/tests/unit/maxmemory.tcl @@ -145,7 +145,6 @@ start_server {tags {"maxmemory" "external:skip"}} { } start_server {tags {"maxmemory external:skip"}} { - test "Without maxmemory small integers are shared" { r config set maxmemory 0 r set a 1 From 9c3068ba23182316118521733d49ed434d4175f7 Mon Sep 17 00:00:00 2001 From: hwware Date: Thu, 21 Nov 2024 16:28:17 +0000 Subject: [PATCH 12/12] update condition in server.maxmemory_soft_scale Signed-off-by: hwware --- src/evict.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evict.c b/src/evict.c index 0ada8573f7..5e901acec8 100644 --- a/src/evict.c +++ b/src/evict.c @@ -738,7 +738,7 @@ int performEvictions(void) { size_t mem_used = zmalloc_used_memory(); size_t overhead = freeMemoryGetNotCountedMemory(); mem_used = (mem_used > overhead) ? mem_used - overhead : 0; - if (mem_used < server.maxmemory) { + if (mem_used < server.maxmemory_soft) { result = EVICT_OK; goto update_metrics; }