Commit ef1ca229 authored by Vitaly Arbuzov's avatar Vitaly Arbuzov
Browse files

Fix eviction

parent 9688f6a4
......@@ -354,7 +354,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
robj *dbRandomKey(redisDb *db) {
dictEntry *de;
int maxtries = 100;
dict *randomDict = getRandomDict(db);
dict *randomDict = getFairRandomDict(db);
while(1) {
sds key;
......@@ -387,7 +387,7 @@ robj *dbRandomKey(redisDb *db) {
}
/* Return random non-empty dictionary from this DB. */
dict *getRandomDict(redisDb *db) {
dict *getFairRandomDict(redisDb *db) {
if (db->dict_count == 1) return db->dict[0];
unsigned long target = randomULong();
unsigned long long int key_count = dbSize(db);
......@@ -407,6 +407,25 @@ dict *getRandomDict(redisDb *db) {
serverPanic("Bug in random dict selection, iteration completed without finding a slot for the target element.");
}
/* Return random non-empty dictionary from this DB by probing random slots.
* This function can be worse than getFairRandomDict in two cases:
* - Dictionary is almost empty, which could result in too much probing.
* - Slot sizes are significantly imbalanced, which could result in unfairness.
* First case is resolved by falling back to getFairRandomDict after certain number of probing attempts.
* Second issue is ignored, meaning that all slots have same probability of being selected. */
dict *getRandomDict(redisDb *db) {
if (db->dict_count == 1 || dbSize(db) == 0) return db->dict[0];
for (int i = 0; i < MAX_RANDOM_DICT_PROBE_ATTEMTPS; i++) {
int candidate = rand() % db->dict_count;
if (dictSize(db->dict[candidate]) > 0) {
return db->dict[candidate];
}
}
/* If we can't find non-empty dict by probing a few random slots, then we'll fall back to slower iterative approach. */
return getFairRandomDict(db);
}
/* Helper for sync and async delete. */
int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {
dictEntry **plink;
......
......@@ -167,7 +167,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, redisDb *db, struct evicti
/* Calculate the idle time according to the policy. This is called
* idle just because the code initially handled LRU, but is in fact
* just a score where an higher score means better candidate. */
* just a score where a higher score means better candidate. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = estimateObjectIdleTime(o);
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
......@@ -569,6 +569,12 @@ int performEvictions(void) {
/* Try to smoke-out bugs (server.also_propagate should be empty here) */
serverAssert(server.also_propagate.numops == 0);
/* Evictions are performed on random keys that have nothing to do with the current command slot. */
int client_slot = -1;
if (server.current_client) {
client_slot = server.current_client->slot;
server.current_client->slot = -1;
}
while (mem_freed < (long long)mem_tofree) {
int j, k, i;
......@@ -592,12 +598,18 @@ int performEvictions(void) {
* every DB. */
for (i = 0; i < server.dbnum; i++) {
db = server.db+i;
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
getRandomDict(db) : db->expires;
if ((keys = dictSize(dict)) != 0) {
evictionPoolPopulate(i, dict, db, pool);
total_keys += keys;
}
do {
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
getRandomDict(db) : db->expires;
if ((keys = dictSize(dict)) != 0) {
evictionPoolPopulate(i, dict, db, pool);
total_keys += keys;
}
/* Since keys are distributed across smaller slot-specific dictionaries in cluster mode, we may need to
* visit more than one dictionary when the number of keys is small. */
} while (keys != 0 && db->dict_count > 1 && server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS &&
total_keys < (unsigned long) server.maxmemory_samples
);
}
if (!total_keys) break; /* No keys to evict. */
......@@ -716,6 +728,7 @@ int performEvictions(void) {
goto cant_free; /* nothing to free... */
}
}
if (client_slot != -1) server.current_client->slot = client_slot; /* Restore client slot for further command processing. */
/* at this point, the memory is OK, or we have reached the time limit */
result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK;
......
......@@ -138,6 +138,7 @@ typedef struct redisObject robj;
#define CONFIG_DEFAULT_PROC_TITLE_TEMPLATE "{title} {listen-addr} {server-mode}"
#define INCREMENTAL_REHASHING_MAX_QUEUE_SIZE (1024*16)
#define INCREMENTAL_REHASHING_THRESHOLD_MS 1
#define MAX_RANDOM_DICT_PROBE_ATTEMTPS 10
/* Bucket sizes for client eviction pools. Each bucket stores clients with
* memory usage of up to twice the size of the bucket below it. */
......@@ -874,7 +875,7 @@ struct RedisModuleDigest {
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
#define LRU_CLOCK_RESOLUTION 1 /* LRU clock resolution in ms */
#define OBJ_SHARED_REFCOUNT INT_MAX /* Global object never destroyed. */
#define OBJ_STATIC_REFCOUNT (INT_MAX-1) /* Object allocated in the stack. */
......@@ -3024,6 +3025,7 @@ void dismissMemoryInChild(void);
int restartServer(int flags, mstime_t delay);
unsigned long long int dbSize(redisDb *db);
dict *getDict(redisDb *db, sds key);
dict *getFairRandomDict(redisDb *db);
dict *getRandomDict(redisDb *db);
unsigned long dbSlots(redisDb *db);
void expandDb(const redisDb *db, uint64_t db_size);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment