mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-24 00:59:02 -05:00
202 lines
7.4 KiB
C
202 lines
7.4 KiB
C
|
#include "server.h"
|
||
|
|
||
|
/* Initialization of the lazy free engine. Must be called only once at server
|
||
|
* startup. */
|
||
|
void initLazyfreeEngine(void) {
|
||
|
server.lazyfree_dbs = listCreate();
|
||
|
server.lazyfree_obj = listCreate();
|
||
|
server.lazyfree_elements = 0;
|
||
|
}
|
||
|
|
||
|
/* Return the amount of work needed in order to free an object.
|
||
|
* The return value is not always the actual number of allocations the
|
||
|
* object is compoesd of, but a number proportional to it.
|
||
|
*
|
||
|
* For strings the function always returns 1.
|
||
|
*
|
||
|
* For aggregated objects represented by hash tables or other data structures
|
||
|
* the function just returns the number of elements the object is composed of.
|
||
|
*
|
||
|
* Objects composed of single allocations are always reported as having a
|
||
|
* single item even if they are actaully logical composed of multiple
|
||
|
* elements.
|
||
|
*
|
||
|
* For lists the funciton returns the number of elements in the quicklist
|
||
|
* representing the list. */
|
||
|
size_t lazyfreeGetFreeEffort(robj *obj) {
|
||
|
if (obj->type == OBJ_LIST) {
|
||
|
quicklist *ql = obj->ptr;
|
||
|
return ql->len;
|
||
|
} else if (obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) {
|
||
|
dict *ht = obj->ptr;
|
||
|
return dictSize(ht);
|
||
|
} else if (obj->type == OBJ_ZSET && obj->encoding == OBJ_ENCODING_SKIPLIST){
|
||
|
zset *zs = obj->ptr;
|
||
|
return zs->zsl->length;
|
||
|
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
|
||
|
dict *ht = obj->ptr;
|
||
|
return dictSize(ht);
|
||
|
} else {
|
||
|
return 1; /* Everything else is a single allocation. */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* This callback is used together with dictScan() in order to free a dict.c
|
||
|
* hash table incrementally. */
|
||
|
void lazyfreeScanCallback(void *privdata, const dictEntry *de) {
|
||
|
dict *ht = privdata;
|
||
|
long saved_iterators = ht->iterators;
|
||
|
ht->iterators = 1; /* Make sure no rehashing happens. */
|
||
|
dictDelete(ht,dictGetKey(de));
|
||
|
ht->iterators = saved_iterators;
|
||
|
}
|
||
|
|
||
|
/* Free some object from the lazy free list. */
|
||
|
#define LAZYFREE_ITER_PER_STEP 100
|
||
|
size_t lazyfreeFastStep(void) {
|
||
|
size_t maxiter = LAZYFREE_ITER_PER_STEP;
|
||
|
size_t workdone = 0;
|
||
|
robj *current = NULL;
|
||
|
|
||
|
while(maxiter--) {
|
||
|
if (current == NULL) {
|
||
|
listNode *ln = listFirst(server.lazyfree_obj);
|
||
|
if (ln == NULL) break; /* Nothing more to free. */
|
||
|
current = ln->value;
|
||
|
}
|
||
|
if ((current->type == OBJ_SET ||
|
||
|
current->type == OBJ_HASH) &&
|
||
|
current->encoding == OBJ_ENCODING_HT)
|
||
|
{
|
||
|
dict *ht = current->ptr;
|
||
|
size_t origsize = dictSize(ht);
|
||
|
ht->iterators = dictScan(ht,ht->iterators,lazyfreeScanCallback,ht);
|
||
|
workdone++; /* We are not sure how many elements we freed, even if
|
||
|
zero, the free list is non empty so we don't return
|
||
|
0 to the caller. */
|
||
|
server.lazyfree_elements -= (origsize - dictSize(ht));
|
||
|
if (dictSize(ht) == 0) {
|
||
|
decrRefCount(current);
|
||
|
listNode *ln = listFirst(server.lazyfree_obj);
|
||
|
listDelNode(server.lazyfree_obj,ln);
|
||
|
current = NULL;
|
||
|
}
|
||
|
} else {
|
||
|
/* Not handled type or encoding. Do a blocking free. */
|
||
|
size_t effort = lazyfreeGetFreeEffort(current);
|
||
|
server.lazyfree_elements -= effort;
|
||
|
workdone += effort;
|
||
|
decrRefCount(current);
|
||
|
listNode *ln = listFirst(server.lazyfree_obj);
|
||
|
listDelNode(server.lazyfree_obj,ln);
|
||
|
current = NULL;
|
||
|
}
|
||
|
}
|
||
|
return workdone;
|
||
|
}
|
||
|
|
||
|
/* Handles slow or fast collection steps. */
|
||
|
size_t lazyfreeStep(int type) {
|
||
|
if (type == LAZYFREE_STEP_FAST) return lazyfreeFastStep();
|
||
|
|
||
|
size_t totalwork = 0;
|
||
|
mstime_t end = mstime()+2;
|
||
|
do {
|
||
|
size_t workdone = lazyfreeFastStep();
|
||
|
if (workdone == 0) break;
|
||
|
totalwork += workdone;
|
||
|
} while(mstime() < end);
|
||
|
return totalwork;
|
||
|
}
|
||
|
|
||
|
/* Delete a key, value, and associated expiration entry if any, from the DB.
|
||
|
* If there are enough allocations to free the value object may be put into
|
||
|
* a lazy free list instead of being freed synchronously. The lazy free list
|
||
|
* will be reclaimed incrementally in a non blocking way. */
|
||
|
#define LAZYFREE_THRESHOLD 64
|
||
|
int dbAsyncDelete(redisDb *db, robj *key) {
|
||
|
/* Deleting an entry from the expires dict will not free the sds of
|
||
|
* the key, because it is shared with the main dictionary. */
|
||
|
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||
|
|
||
|
/* If the value is composed of a few allocations, to free in a lazy way
|
||
|
* is actually just slower... So under a certain limit we just free
|
||
|
* the object synchronously. */
|
||
|
dictEntry *de = dictFind(db->dict,key->ptr);
|
||
|
if (de) {
|
||
|
robj *val = dictGetVal(de);
|
||
|
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||
|
|
||
|
/* If releasing the object is too much work, let's put it into the
|
||
|
* lazy free list. */
|
||
|
if (free_effort > LAZYFREE_THRESHOLD) {
|
||
|
listAddNodeTail(server.lazyfree_obj,val);
|
||
|
server.lazyfree_elements += free_effort;
|
||
|
dictSetVal(db->dict,de,NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Release the key-val pair, or just the key if we set the val
|
||
|
* field to NULL in order to lazy free it later. */
|
||
|
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
|
||
|
if (server.cluster_enabled) slotToKeyDel(key);
|
||
|
return 1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* This is the timer handler we use to incrementally perform collection
|
||
|
* into the lazy free lists. We can't use serverCron since we need a
|
||
|
* very high timer frequency when there are many objects to collect, while
|
||
|
* we lower the frequency to just 1HZ when there is nothing to do.
|
||
|
*
|
||
|
* Since a slow lazy free step will take 1.5 milliseconds and we modulate
|
||
|
* the timer frequency from 1 to 333 HZ in an adaptive way, the CPU
|
||
|
* used is between 0% (nothing in the lazy free list) to 50%.
|
||
|
*
|
||
|
* The frequency is obtained as follows: if the lazy free list is empty
|
||
|
* it is set to 1HZ. If the lazy free has elements the call period starts
|
||
|
* at 20 (50HZ) and is decremented (up to 3 ms = 333HZ) each time the server
|
||
|
* used memory raises between calls of this function. */
|
||
|
int lazyfreeCron(struct aeEventLoop *eventLoop, long long id, void *clientData)
|
||
|
{
|
||
|
UNUSED(eventLoop);
|
||
|
UNUSED(id);
|
||
|
UNUSED(clientData);
|
||
|
|
||
|
static size_t prev_mem;
|
||
|
static int timer_period = 1000; /* Defauls to 1HZ */
|
||
|
static double mem_trend = 0;
|
||
|
size_t mem = zmalloc_used_memory();
|
||
|
|
||
|
/* Compute the memory trend, biased towards thinking memory is raising
|
||
|
* for a few calls every time previous and current memory raise. */
|
||
|
if (prev_mem < mem) mem_trend = 1;
|
||
|
mem_trend *= 0.9; /* Make it slowly forget. */
|
||
|
int mem_is_raising = mem_trend > .1;
|
||
|
|
||
|
/* Free a few items. */
|
||
|
size_t workdone = lazyfreeStep(LAZYFREE_STEP_SLOW);
|
||
|
|
||
|
/* Adjust this timer call frequency according to the current state. */
|
||
|
if (workdone) {
|
||
|
if (timer_period == 1000) timer_period = 20;
|
||
|
if (mem_is_raising && timer_period > 3)
|
||
|
timer_period--; /* Raise call frequency. */
|
||
|
else if (!mem_is_raising && timer_period < 20)
|
||
|
timer_period++; /* Lower call frequency. */
|
||
|
} else {
|
||
|
timer_period = 1000; /* 1 HZ */
|
||
|
}
|
||
|
prev_mem = mem;
|
||
|
#if 0
|
||
|
printf("%llu (%d hz) %s (%f)\n",
|
||
|
(unsigned long long)server.lazyfree_elements,
|
||
|
1000/timer_period,
|
||
|
mem_is_raising ? "RAISING" : "lowering",
|
||
|
mem_trend);
|
||
|
#endif
|
||
|
return timer_period;
|
||
|
}
|