Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maxmemory reserved parameter code format #21

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -2496,9 +2496,35 @@ static int updateReplBacklogSize(const char **err) {
return 1;
}

static int updateMaxmemoryReserved(const char **err) {
if (server.maxmemory) {
if (server.maxmemory < server.maxmemory_reserved) {
*err = "The maxmemory value is smaller than the new reserved memory buffer set via CONFIG SET";
return 0;
}
server.key_eviction_memory = server.maxmemory - server.maxmemory_reserved;
size_t used = zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
if (server.key_eviction_memory < used) {
serverLog(LL_WARNING, "WARNING: the difference between memory usage and maxmemory is less than reserved memory. "
"This will result in key eviction depending on the maxmemory-policy. But server can still accept new write commands.");
}
startEvictionTimeProc();
} else {
if (server.maxmemory_reserved) {
*err = "Current maxmemory value is 0, the new reserved memory buffer value is invalid";
return 0;
}
server.key_eviction_memory = 0;
}
return 1;
}

static int updateMaxmemory(const char **err) {
UNUSED(err);
if (server.maxmemory) {
if (server.maxmemory < server.maxmemory_reserved) {
*err = "The new maxmemory value set via CONFIG SET is smaller than the existing reserved memory buffer";
return 0;
}
size_t used = zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
if (server.maxmemory < used) {
serverLog(LL_WARNING,
Expand All @@ -2507,7 +2533,11 @@ static int updateMaxmemory(const char **err) {
"depending on the maxmemory-policy.",
server.maxmemory, used);
}
server.key_eviction_memory = server.maxmemory - server.maxmemory_reserved;
startEvictionTimeProc();
} else {
server.maxmemory_reserved = 0;
server.key_eviction_memory = 0;
}
return 1;
}
Expand Down Expand Up @@ -3329,6 +3359,7 @@ standardConfig static_configs[] = {

/* Unsigned Long Long configs */
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
createULongLongConfig("maxmemory-reserved", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory_reserved, 0, MEMORY_CONFIG, NULL, updateMaxmemoryReserved),
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 */
Expand Down
44 changes: 29 additions & 15 deletions src/evict.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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. */
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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) {
Expand All @@ -726,7 +740,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.key_eviction_memory) == C_OK) {
result = EVICT_OK;
break;
}
Expand Down
6 changes: 3 additions & 3 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -4007,7 +4007,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;

Expand Down Expand Up @@ -6405,7 +6405,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;
}
Expand Down Expand Up @@ -10956,7 +10956,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;
}

Expand Down
14 changes: 14 additions & 0 deletions src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -2785,6 +2785,16 @@ void initServer(void) {
server.client_mem_usage_buckets = NULL;
resetReplicationBuffer();

if (server.maxmemory) {
if (server.maxmemory < server.maxmemory_reserved) {
serverLog(LL_WARNING, "Failed to config reserved memory buffer, which is greater than maxmemory.");
exit(1);
}
server.key_eviction_memory = server.maxmemory - server.maxmemory_reserved;
} 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.");
Expand Down Expand Up @@ -5717,6 +5727,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();
Expand All @@ -5738,6 +5749,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(
Expand Down Expand Up @@ -5776,6 +5788,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,
Expand Down
4 changes: 3 additions & 1 deletion src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,8 @@ 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 maxmemory_reserved; /* Memory bytes to be away from maxmemory*/
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 */
Expand Down Expand Up @@ -3137,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);
Expand Down
65 changes: 64 additions & 1 deletion tests/unit/maxmemory.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,71 @@ start_server {tags {"maxmemory external:skip"}} {
}
}
}
}

test "enable maxmemory-reserved, available memory could change with maxmemory update" {
# make sure to start with a blank instance
r flushall
# we set maxmemory as 0, and we expect maxmemory-reserved as 0 too.
r config set maxmemory 0
assert_equal 0 [lindex [r config get maxmemory] 1]
assert_equal 0 [lindex [r config get maxmemory-reserved] 1]
# we increase maxmemory, and we expect more memory available (key_eviction_memory field value increases too).
r config set maxmemory 10000000
assert_equal 0 [lindex [r config get maxmemory-reserved] 1]
set info_memory [r info memory]
assert_equal [getInfoProperty $info_memory key_eviction_memory] 10000000
# we set maxmemory-reserved as non-zero value first
r config set maxmemory-reserved 4000000
set info_memory [r info memory]
assert_equal [getInfoProperty $info_memory key_eviction_memory] 6000000
# we decrease maxmemory, and we expect less memory available (key_eviction_memory field value decreases too).
r config set maxmemory 8000000
set info_memory [r info memory]
assert_equal [getInfoProperty $info_memory key_eviction_memory] 4000000
}

foreach policy {
allkeys-random allkeys-lru allkeys-lfu volatile-lru volatile-lfu volatile-random volatile-ttl
} {
test "enable maxmemory-reserved, test eviction key number with policy ($policy) and different maxmemory value" {
r flushall
r config set maxmemory 0
# make sure to start with a blank instance
set num_eviction_key_init [s evicted_keys]
set used 1165448
set limit_maxmemory_value1 [expr {$used+40*1024}]
set limit_maxmemory_value2 [expr {$used+70*1024}]
set limit_maxmemory_value3 [expr {$used+100*1024}]
r config set maxmemory $limit_maxmemory_value1
r config set maxmemory-policy $policy
r config set maxmemory-reserved 30720
set numkeys 5000
for {set j 0} {$j < $numkeys} {incr j} {
catch {r set $j $j EX 10000}
}
set num_eviction_key_maxmemory_1 [s evicted_keys]
set diff_num_key_eviction_one [expr {$num_eviction_key_maxmemory_1 - $num_eviction_key_init}]
r flushall
r config set maxmemory $limit_maxmemory_value2
for {set j 0} {$j < $numkeys} {incr j} {
catch {r set $j $j EX 10000}
}
set num_eviction_key_maxmemory_2 [s evicted_keys]
set diff_num_key_eviction_two [expr {$num_eviction_key_maxmemory_2 - $num_eviction_key_maxmemory_1}]
r flushall
r config set maxmemory $limit_maxmemory_value3
for {set j 0} {$j < $numkeys} {incr j} {
catch {r set $j $j EX 10000}
}
set num_eviction_key_maxmemory_3 [s evicted_keys]
set diff_num_key_eviction_three [expr {$num_eviction_key_maxmemory_3 - $num_eviction_key_maxmemory_2}]
assert_morethan $diff_num_key_eviction_two $diff_num_key_eviction_three
assert_morethan $diff_num_key_eviction_one $diff_num_key_eviction_two
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"]
Expand Down
6 changes: 6 additions & 0 deletions valkey.conf
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,12 @@ acllog-max-len 128
#
# maxmemory-policy noeviction

# `maxmemory-reserved` defines a fix amount of reserved maxmemory away from maxmemory.
# When the difference between memory usage and maxmemory is less than it, Valkey begins proactive key eviction. However, exceeding this
# threshold does not immediately reject new write commands; only the hard `maxmemory` limit will do so.
#
# maxmemory-reserved <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
Expand Down
Loading