Process the combined LRU lists in LRU order

Only cleanup headers that are less than equal to the rbt's last_used
time.  Adjust the rbt's last_used time when the target cleaning was
not achieved to the oldest value of the remaining set of headers.

When updating delegating NS and glue records last_used was not being
updated when it should have been.

When adding zero TTL records to the tail of the LRU lists set
last_used to rbtdb->last_used + 1 rather than now.  This appoximately
preserves the lists LRU order.
This commit is contained in:
Mark Andrews
2023-11-23 15:47:35 +11:00
parent 8af6fc44e0
commit 5e8f0e9ceb
3 changed files with 73 additions and 18 deletions

View File

@@ -1635,7 +1635,9 @@ expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum,
size_t purged = 0;
for (header = ISC_LIST_TAIL(rbtdb->lru[locknum]);
header != NULL && purged <= purgesize; header = header_prev)
header != NULL && header->last_used <= rbtdb->last_used &&
purged <= purgesize;
header = header_prev)
{
size_t header_size = rdataset_size(header);
header_prev = ISC_LIST_PREV(header, link);
@@ -1662,25 +1664,22 @@ expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum,
* we clean up entries up to the size of newly added rdata that triggered
* the overmem; this is accessible via newheader.
*
* This process is triggered while adding a new entry, and we specifically
* avoid purging entries in the same LRU bucket as the one to which the new
* entry will belong. Otherwise, we might purge entries of the same name
* of different RR types while adding RRsets from a single response
* (consider the case where we're adding A and AAAA glue records of the
* same NS name).
* The LRU lists tails are processed in LRU order to the nearest second.
*
* A write lock on the tree must be held.
*/
void
dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader,
unsigned int locknum_start,
isc_rwlocktype_t *tlocktypep DNS__DB_FLARG) {
unsigned int locknum;
uint32_t locknum_start = rbtdb->lru_sweep++ % rbtdb->node_lock_count;
uint32_t locknum = locknum_start;
size_t purgesize = rdataset_size(newheader);
size_t purged = 0;
isc_stdtime_t min_last_used = 0;
size_t max_passes = 8;
for (locknum = (locknum_start + 1) % rbtdb->node_lock_count;
locknum != locknum_start && purged <= purgesize;
locknum = (locknum + 1) % rbtdb->node_lock_count)
{
again:
do {
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
NODE_WRLOCK(&rbtdb->node_locks[locknum].lock, &nlocktype);
@@ -1688,6 +1687,30 @@ dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader,
purgesize -
purged DNS__DB_FLARG_PASS);
/*
* Work out the oldest remaining last_used values of the list
* tails as we walk across the array of lru lists.
*/
dns_slabheader_t *header = ISC_LIST_TAIL(rbtdb->lru[locknum]);
if (header != NULL &&
(min_last_used == 0 || header->last_used < min_last_used))
{
min_last_used = header->last_used;
}
NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, &nlocktype);
locknum = (locknum + 1) % rbtdb->node_lock_count;
} while (locknum != locknum_start && purged <= purgesize);
/*
* Update rbtdb->last_used if we have walked all the list tails and have
* not freed the required amount of memory.
*/
if (purged < purgesize) {
if (min_last_used != 0) {
rbtdb->last_used = min_last_used;
if (max_passes-- > 0) {
goto again;
}
}
}
}

View File

@@ -2757,6 +2757,15 @@ find_header:
if (header->ttl > newheader->ttl) {
dns__rbtdb_setttl(header, newheader->ttl);
}
if (header->last_used != now) {
ISC_LIST_UNLINK(
rbtdb->lru[HEADER_NODE(header)->locknum],
header, link);
header->last_used = now;
ISC_LIST_PREPEND(
rbtdb->lru[HEADER_NODE(header)->locknum],
header, link);
}
if (header->noqname == NULL &&
newheader->noqname != NULL)
{
@@ -2811,6 +2820,15 @@ find_header:
if (header->ttl > newheader->ttl) {
dns__rbtdb_setttl(header, newheader->ttl);
}
if (header->last_used != now) {
ISC_LIST_UNLINK(
rbtdb->lru[HEADER_NODE(header)->locknum],
header, link);
header->last_used = now;
ISC_LIST_PREPEND(
rbtdb->lru[HEADER_NODE(header)->locknum],
header, link);
}
if (header->noqname == NULL &&
newheader->noqname != NULL)
{
@@ -2839,6 +2857,8 @@ find_header:
idx = HEADER_NODE(newheader)->locknum;
if (IS_CACHE(rbtdb)) {
if (ZEROTTL(newheader)) {
newheader->last_used =
rbtdb->last_used + 1;
ISC_LIST_APPEND(rbtdb->lru[idx],
newheader, link);
} else {
@@ -2882,6 +2902,8 @@ find_header:
isc_heap_insert(rbtdb->heaps[idx], newheader);
newheader->heap = rbtdb->heaps[idx];
if (ZEROTTL(newheader)) {
newheader->last_used =
rbtdb->last_used + 1;
ISC_LIST_APPEND(rbtdb->lru[idx],
newheader, link);
} else {
@@ -3263,7 +3285,7 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node,
}
if (cache_is_overmem) {
dns__cachedb_overmem(rbtdb, newheader, rbtnode->locknum,
dns__cachedb_overmem(rbtdb, newheader,
&tlocktype DNS__DB_FLARG_PASS);
}

View File

@@ -273,12 +273,23 @@ struct dns_rbtdb {
uint32_t serve_stale_refresh;
/*
* This is a linked list used to implement the LRU cache. There will
* be node_lock_count linked lists here. Nodes in bucket 1 will be
* placed on the linked list lru[1].
* This is an array of linked lists used to implement the LRU cache.
* There will be node_lock_count linked lists here. Nodes in bucket 1
* will be placed on the linked list lru[1].
*/
dns_slabheaderlist_t *lru;
/*
* Start point % node_lock_count for next LRU cleanup.
*/
atomic_uint lru_sweep;
/*
* When performing LRU cleaning limit cleaning to headers that were
* last used at or before this.
*/
_Atomic(isc_stdtime_t) last_used;
/*%
* Temporary storage for stale cache nodes and dynamically deleted
* nodes that await being cleaned up.
@@ -607,7 +618,6 @@ dns__cachedb_expireheader(dns_slabheader_t *header,
dns_expire_t reason DNS__DB_FLARG);
void
dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader,
unsigned int locknum_start,
isc_rwlocktype_t *tlocktypep DNS__DB_FLARG);
ISC_LANG_ENDDECLS