Merge 3e2f561f60191335df351a94c707ffb09f74462b into 26c6f1af9b29d525831c7fa9840ab3e47ed7b700

This commit is contained in:
Wen Hui 2025-02-01 14:48:57 -08:00 committed by GitHub
commit 79eee406df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 179 additions and 20 deletions

View File

@ -2494,6 +2494,42 @@ static int updateReplBacklogSize(const char **err) {
return 1;
}
static int updateKeyEvictionMemory(const char **err) {
UNUSED(err);
if (server.maxmemory) {
if (!server.key_eviction_memory) {
serverLog(LL_WARNING,
"WARNING: current maxmemory value is not 0, the new key-eviction-memory value set via CONFIG SET (%llu) is "
"0. The new key-eviction-memory value is set to equal to current maxmemory (%llu) ",
server.key_eviction_memory, server.maxmemory);
server.key_eviction_memory = server.maxmemory;
} else if (server.key_eviction_memory > server.maxmemory) {
serverLog(LL_WARNING,
"WARNING: the new key-eviction-memory value set via CONFIG SET (%llu) is greater than current maxmemory, "
"The new key-eviction-memory value is set to equal to current maxmemory (%llu) ",
server.key_eviction_memory, server.maxmemory);
server.key_eviction_memory = server.maxmemory;
}
size_t used = zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
if (server.key_eviction_memory < used) {
serverLog(LL_WARNING,
"WARNING: the new key-eviction-memorym value set via CONFIG SET (%llu) is smaller than the current memory "
"usage (%zu). This will result in key eviction and/or the inability to accept new write commands "
"depending on the maxmemory-policy.",
server.key_eviction_memory, used);
}
} else {
if (server.key_eviction_memory) {
serverLog(LL_WARNING,
"WARNING: current maxmemory value is 0, the new key-eviction-memory value set via CONFIG SET (%llu) is "
"greater than 0. The new key-eviction-memory value is invalid, and its value is set to 0 ",
server.key_eviction_memory);
}
server.key_eviction_memory = 0;
}
return 1;
}
static int updateMaxmemory(const char **err) {
UNUSED(err);
if (server.maxmemory) {
@ -2505,7 +2541,12 @@ static int updateMaxmemory(const char **err) {
"depending on the maxmemory-policy.",
server.maxmemory, used);
}
if (!server.key_eviction_memory || server.key_eviction_memory > server.maxmemory) {
server.key_eviction_memory = server.maxmemory;
}
startEvictionTimeProc();
} else {
server.key_eviction_memory = 0;
}
return 1;
}
@ -3327,6 +3368,7 @@ standardConfig static_configs[] = {
/* Unsigned Long Long configs */
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
createULongLongConfig("key-eviction-memory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.key_eviction_memory, 0, MEMORY_CONFIG, NULL, updateKeyEvictionMemory),
createULongLongConfig("cluster-link-sendbuf-limit", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.cluster_link_msg_queue_limit_bytes, 0, MEMORY_CONFIG, NULL, NULL),
/* Size_t configs */

View File

@ -377,7 +377,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 maxmemory) {
size_t mem_reported, mem_used, mem_tofree;
/* Check if we are over the memory usage limit. If we are not, no need
@ -386,11 +386,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 (!maxmemory) {
if (level) *level = 0;
return C_OK;
}
if (mem_reported <= server.maxmemory && !level) return C_OK;
if (mem_reported <= maxmemory && !level) return C_OK;
/* Remove the size of replicas output buffers and AOF buffer from the
* count of used memory. */
@ -399,15 +400,19 @@ 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) {
*level = (float)mem_used / (float)server.maxmemory;
}
if (mem_reported <= server.maxmemory) return C_OK;
if (mem_reported <= maxmemory) return C_OK;
/* Check if we are still over the memory limit. */
if (mem_used <= server.maxmemory) return C_OK;
/* if function parameter 'maxmemory' is equal to maxmemory and mem_used > maxmemory then OOM /
/ if function parameter 'maxmemory' is equal to maxmemory_soft and mem_used > function parameter 'maxmemory' then there is no OOM but eviction happens */
if (mem_used <= maxmemory) return C_OK;
/* Compute how much memory we need to free. */
mem_tofree = mem_used - server.maxmemory;
mem_tofree = mem_used - server.key_eviction_memory;
if (logical) *logical = mem_used;
if (tofree) *tofree = mem_tofree;
@ -522,20 +527,24 @@ int performEvictions(void) {
if (!isSafeToPerformEvictions()) return EVICT_OK;
int keys_freed = 0;
size_t mem_reported, mem_tofree;
size_t mem_reported, mem_tofree, mem_used;
long long mem_freed = 0; /* Maybe become negative */
mstime_t latency, eviction_latency;
long long delta;
int replicas = listLength(server.replicas);
int result = EVICT_FAIL;
if (getMaxmemoryState(&mem_reported, NULL, &mem_tofree, NULL) == C_OK) {
if (getMaxmemoryState(&mem_reported, &mem_used, &mem_tofree, NULL, server.key_eviction_memory) == C_OK) {
result = EVICT_OK;
goto update_metrics;
}
if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION || (iAmPrimary() && server.import_mode)) {
result = EVICT_FAIL; /* We need to free memory, but policy forbids or we are in import mode. */
if (mem_used >= server.maxmemory) {
result = EVICT_FAIL; /* We need to free memory, but policy forbids or we are in import mode. */
} else {
result = EVICT_OK; /* used_memory greater than key_eviction_memory, but not reach OOM */
}
goto update_metrics;
}
@ -697,7 +706,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.key_eviction_memory) == C_OK) {
break;
}
}
@ -712,11 +721,16 @@ int performEvictions(void) {
}
}
} else {
goto cant_free; /* nothing to free... */
break;
}
}
/* at this point, the memory is OK, or we have reached the time limit */
result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK;
if (mem_freed >= (long long)(mem_used - server.key_eviction_memory)) {
/* at this point, the memory is OK, or we have reached the time limit */
result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK;
} else {
goto cant_free;
}
cant_free:
if (result == EVICT_FAIL) {
@ -726,7 +740,7 @@ cant_free:
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.key_eviction_memory) == C_OK) {
result = EVICT_OK;
break;
}

View File

@ -4006,7 +4006,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;
@ -6404,7 +6404,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;
}
@ -10952,7 +10952,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;
}

View File

@ -2774,6 +2774,14 @@ void initServer(void) {
server.client_mem_usage_buckets = NULL;
resetReplicationBuffer();
if (server.maxmemory) {
if (!server.key_eviction_memory || server.key_eviction_memory > server.maxmemory) {
server.key_eviction_memory = server.maxmemory;
}
} else {
server.key_eviction_memory = 0;
}
/* 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.");
@ -5705,6 +5713,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 key_eviction_memory_hmem[64];
size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size;
const char *evict_policy = evictPolicyToString();
@ -5726,6 +5735,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(key_eviction_memory_hmem, sizeof(key_eviction_memory_hmem), server.key_eviction_memory);
if (sections++) info = sdscat(info, "\r\n");
info = sdscatprintf(
@ -5764,6 +5774,8 @@ 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,
"key_eviction_memory:%lld\r\n", server.key_eviction_memory,
"key_eviction_memory_human:%s\r\n", key_eviction_memory_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,

View File

@ -1991,6 +1991,7 @@ struct valkeyServer {
/* Limits */
unsigned int maxclients; /* Max number of simultaneous clients */
unsigned long long maxmemory; /* Max number of memory bytes to use */
unsigned long long key_eviction_memory; /* Memory bytes to begin the key eviction process */
ssize_t maxmemory_clients; /* Memory limit for total client buffers */
int maxmemory_policy; /* Policy for key eviction */
int maxmemory_samples; /* Precision of random sampling */
@ -3138,7 +3139,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 maxmemory);
size_t freeMemoryGetNotCountedMemory(void);
int overMaxmemoryAfterAlloc(size_t moremem);
uint64_t getCommandFlags(client *c);

View File

@ -174,6 +174,7 @@ start_server {tags {"maxmemory external:skip"}} {
r setex [randomKey] 10000 x
}
assert {[s used_memory] < ($limit+4096)}
r config set maxmemory 0
}
}
@ -216,6 +217,7 @@ start_server {tags {"maxmemory external:skip"}} {
} else {
assert {$err == 1}
}
r config set maxmemory 0
}
}
@ -261,10 +263,91 @@ start_server {tags {"maxmemory external:skip"}} {
for {set j 0} {$j < $numkeys} {incr j 2} {
assert {[r exists "key:$j"]}
}
r config set maxmemory 0
}
}
}
test "key-eviction-memory could change with maxmemory update" {
# make sure to start with a blank instance
r flushall
# we set maxmemory as 0, and we expect key-eviction-memory as 0 too.
r config set maxmemory 0
assert_equal 0 [lindex [r config get maxmemory] 1]
assert_equal 0 [lindex [r config get key-eviction-memory] 1]
# we increase maxmemory, and we expect key-eviction-memory value is increased too.
r config set maxmemory 10mb
set key_eviction_memory [lindex [r config get key-eviction-memory] 1]
assert_equal [lindex [r config get maxmemory] 1] $key_eviction_memory
# we increase maxmemory a little bit, but we expect key-eviction-memory value no change.
r config set maxmemory 11mb
assert_equal $key_eviction_memory [lindex [r config get key-eviction-memory] 1]
assert_morethan [lindex [r config get maxmemory] 1] [lindex [r config get key-eviction-memory] 1]
# we decrease maxmemory lower than key-eviction-memory, thus we expect key-eviction-memory value decrease to maxmemory value.
r config set maxmemory 6mb
assert_equal [lindex [r config get maxmemory] 1] [lindex [r config get key-eviction-memory] 1]
# we decrease maxmemory to 0, and we expect key-eviction-memory value decrease to 0 too.
r config set maxmemory 0
assert_equal 0 [lindex [r config get maxmemory] 1]
assert_equal 0 [lindex [r config get key-eviction-memory] 1]
}
test "key-eviction-memory update test" {
# make sure to start with a blank instance
r flushall
# If maxmemory is not 0, and key-eviction-memory is set to 0, we expect the key-eviction-memory equal to maxmemory
r config set maxmemory 10mb
r config set key-eviction-memory 0
assert_equal [lindex [r config get maxmemory] 1] [lindex [r config get key-eviction-memory] 1]
# we increase key-eviction-memory, but its value is still less than maxmemory.
r config set key-eviction-memory 5mb
assert_morethan [lindex [r config get maxmemory] 1] [lindex [r config get key-eviction-memory] 1]
# we increase key-eviction-memory more than maxmemory, but we expect its value is equal to maxmemory.
r config set key-eviction-memory 10mb
assert_equal [lindex [r config get maxmemory] 1] [lindex [r config get key-eviction-memory] 1]
r config set maxmemory 0
}
foreach policy {
allkeys-random allkeys-lru allkeys-lfu volatile-lru volatile-lfu volatile-random volatile-ttl
} {
test "key-eviction-memory - test eviction key number with policy ($policy) and different key-eviction-memory value" {
r flushall
# make sure to start with a blank instance
set num_eviction_key_init [s evicted_keys]
set used 1134728
set limit_maxmemory [expr {$used+100*1024}]
set limit_key_eviction_memory_threshold1 [expr {$used+70*1024}]
set limit_key_eviction_memory_threshold2 [expr {$used+40*1024}]
r config set maxmemory $limit_maxmemory
r config set maxmemory-policy $policy
set numkeys 5000
for {set j 0} {$j < $numkeys} {incr j} {
catch {r set $j $j EX 10000}
}
set num_eviction_key_maxmemory [s evicted_keys]
set diff_num_key_eviction_one [expr {$num_eviction_key_maxmemory - $num_eviction_key_init}]
r flushall
r config set key-eviction-memory $limit_key_eviction_memory_threshold1
for {set j 0} {$j < $numkeys} {incr j} {
catch {r set $j $j EX 10000}
}
set num_eviction_key_threshold1 [s evicted_keys]
set diff_num_key_eviction_two [expr {$num_eviction_key_threshold1 - $num_eviction_key_maxmemory}]
r flushall
r config set key-eviction-memory $limit_key_eviction_memory_threshold2
for {set j 0} {$j < $numkeys} {incr j} {
catch {r set $j $j EX 10000}
}
set num_eviction_key_threshold2 [s evicted_keys]
set diff_num_key_eviction_three [expr {$num_eviction_key_threshold2 - $num_eviction_key_threshold1}]
assert_morethan $diff_num_key_eviction_three $diff_num_key_eviction_two
assert_morethan $diff_num_key_eviction_two $diff_num_key_eviction_one
r flushall
r config set maxmemory 0
}
}
}
# Calculate query buffer memory of slave
proc slave_query_buffer {srv} {
set clients [split [$srv client list] "\r\n"]

View File

@ -66,6 +66,7 @@ run_solo {defrag} {
r config set latency-monitor-threshold 5
r latency reset
r config set maxmemory 110mb ;# prevent further eviction (not to fail the digest test)
r config set key-eviction-memory 110mb
set digest [debug_digest]
catch {r config set activedefrag yes} e
if {[r config get activedefrag] eq "activedefrag yes"} {

View File

@ -1279,6 +1279,12 @@ acllog-max-len 128
#
# maxmemory-policy noeviction
# `key-eviction-memory` defines a "soft" maxmemory threshold as the `maxmemory` limit.
# When memory usage exceeds this key-eviction-memory, Valkey begins proactive key eviction. However, exceeding this
# threshold does not immediately reject new write commands; only the hard `maxmemory` limit will do so.
#
# key-eviction-memory <bytes>
# 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