[9.20] chg: usr: Fix CPU spikes and slow queries when cache approaches memory limit

When the cache grew close to the configured max-cache-size, every
subsequent entry triggered all worker threads to run cache cleanup at
once, causing CPU spikes and a drop in query throughput. Cleanup is now
spread probabilistically across inserts as memory approaches the limit,
so the work is distributed evenly instead of piling up at the threshold.

Backport of MR !1002

Merge branch '5891-improve-overmem-cleaning-9.20' into 'security-bind-9.20'

See merge request isc-private/bind9!1000
This commit is contained in:
Ondřej Surý 2026-05-05 14:55:39 +02:00 committed by Michał Kępień
commit 9a7f5627e0
No known key found for this signature in database
3 changed files with 42 additions and 52 deletions

View file

@ -29,6 +29,7 @@
#include <isc/once.h>
#include <isc/os.h>
#include <isc/overflow.h>
#include <isc/random.h>
#include <isc/refcount.h>
#include <isc/strerr.h>
#include <isc/string.h>
@ -131,7 +132,6 @@ struct isc_mem {
char name[16];
atomic_size_t inuse;
atomic_bool hi_called;
atomic_bool is_overmem;
atomic_size_t hi_water;
atomic_size_t lo_water;
ISC_LIST(isc_mempool_t) pools;
@ -570,7 +570,6 @@ mem_create(isc_mem_t **ctxp, unsigned int debugging, unsigned int flags,
atomic_init(&ctx->hi_water, 0);
atomic_init(&ctx->lo_water, 0);
atomic_init(&ctx->hi_called, false);
atomic_init(&ctx->is_overmem, false);
ISC_LIST_INIT(ctx->pools);
@ -1017,48 +1016,30 @@ bool
isc_mem_isovermem(isc_mem_t *ctx) {
REQUIRE(VALID_CONTEXT(ctx));
bool is_overmem = atomic_load_relaxed(&ctx->is_overmem);
if (!is_overmem) {
/* We are not overmem, check whether we should be? */
size_t hiwater = atomic_load_relaxed(&ctx->hi_water);
if (hiwater == 0) {
return false;
}
size_t inuse = atomic_load_relaxed(&ctx->inuse);
if (inuse <= hiwater) {
return false;
}
if ((isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) {
fprintf(stderr,
"overmem mctx %p inuse %zu hi_water %zu\n", ctx,
inuse, hiwater);
}
atomic_store_relaxed(&ctx->is_overmem, true);
return true;
} else {
/* We are overmem, check whether we should not be? */
size_t lowater = atomic_load_relaxed(&ctx->lo_water);
if (lowater == 0) {
return false;
}
size_t inuse = atomic_load_relaxed(&ctx->inuse);
if (inuse >= lowater) {
return true;
}
if ((isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) {
fprintf(stderr,
"overmem mctx %p inuse %zu lo_water %zu\n", ctx,
inuse, lowater);
}
atomic_store_relaxed(&ctx->is_overmem, false);
size_t hiwater = atomic_load_relaxed(&ctx->hi_water);
if (hiwater == 0) {
return false;
}
size_t inuse = atomic_load_relaxed(&ctx->inuse);
if (inuse >= hiwater) {
return true;
}
size_t lowater = atomic_load_relaxed(&ctx->lo_water);
if (inuse <= lowater) {
return false;
}
/*
* Between lo_water and hi_water, return true with a probability
* that ramps linearly from 0 at lo_water to 1 at hi_water. This
* spreads cache cleaning across many inserts instead of triggering
* a thundering herd once the hi_water mark is crossed.
*/
uint32_t prob = (uint32_t)(((uint64_t)(inuse - lowater) * 256) /
(hiwater - lowater));
return isc_random8() < prob;
}
void

View file

@ -137,7 +137,6 @@ ISC_LOOP_TEST_IMPL(overmempurge_bigrdata) {
for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) {
overmempurge_addrdataset(db, now, i, 50053, 0, false);
}
assert_true(isc_mem_isovermem(mctx2));
/*
* Then try to add the same number of entries, each has very large data.
@ -188,7 +187,6 @@ ISC_LOOP_TEST_IMPL(overmempurge_longname) {
for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) {
overmempurge_addrdataset(db, now, i, 50053, 0, false);
}
assert_true(isc_mem_isovermem(mctx2));
/*
* Then try to add the same number of entries, each has very long name.

View file

@ -291,6 +291,17 @@ ISC_RUN_TEST_IMPL(isc_mem_reallocate) {
isc_mem_free(mctx, data);
}
static bool
at_least_one_overmem(isc_mem_t *omctx) {
for (size_t i = 0; i < UINT16_MAX; i++) {
/* The overmem is probability based in this range */
if (isc_mem_isovermem(omctx)) {
return true;
}
}
return false;
}
ISC_RUN_TEST_IMPL(isc_mem_overmem) {
isc_mem_t *omctx = NULL;
isc_mem_create(&omctx);
@ -298,27 +309,27 @@ ISC_RUN_TEST_IMPL(isc_mem_overmem) {
isc_mem_setwater(omctx, 1024, 512);
/* inuse < lo_water */
/* inuse <= lo_water is always false */
void *data1 = isc_mem_allocate(omctx, 256);
assert_false(isc_mem_isovermem(omctx));
/* lo_water < inuse < hi_water */
/* lo_water < inuse < hi_water might be true or false */
void *data2 = isc_mem_allocate(omctx, 512);
assert_false(isc_mem_isovermem(omctx));
assert_true(at_least_one_overmem(omctx));
/* hi_water < inuse */
/* hi_water <= inuse is always true */
void *data3 = isc_mem_allocate(omctx, 512);
assert_true(isc_mem_isovermem(omctx));
/* lo_water < inuse < hi_water */
/* lo_water < inuse < hi_water might be true or false */
isc_mem_free(omctx, data2);
assert_true(isc_mem_isovermem(omctx));
assert_true(at_least_one_overmem(omctx));
/* inuse < lo_water */
/* inuse <= lo_water is always false */
isc_mem_free(omctx, data3);
assert_false(isc_mem_isovermem(omctx));
/* inuse == 0 */
/* inuse == 0 is always false */
isc_mem_free(omctx, data1);
assert_false(isc_mem_isovermem(omctx));