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) { ...@@ -354,7 +354,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
robj *dbRandomKey(redisDb *db) { robj *dbRandomKey(redisDb *db) {
dictEntry *de; dictEntry *de;
int maxtries = 100; int maxtries = 100;
dict *randomDict = getRandomDict(db); dict *randomDict = getFairRandomDict(db);
while(1) { while(1) {
sds key; sds key;
...@@ -387,7 +387,7 @@ robj *dbRandomKey(redisDb *db) { ...@@ -387,7 +387,7 @@ robj *dbRandomKey(redisDb *db) {
} }
/* Return random non-empty dictionary from this 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]; if (db->dict_count == 1) return db->dict[0];
unsigned long target = randomULong(); unsigned long target = randomULong();
unsigned long long int key_count = dbSize(db); unsigned long long int key_count = dbSize(db);
...@@ -407,6 +407,25 @@ dict *getRandomDict(redisDb *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."); 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. */ /* Helper for sync and async delete. */
int dbGenericDelete(redisDb *db, robj *key, int async, int flags) { int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {
dictEntry **plink; dictEntry **plink;
......
...@@ -167,7 +167,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, redisDb *db, struct evicti ...@@ -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 /* Calculate the idle time according to the policy. This is called
* idle just because the code initially handled LRU, but is in fact * 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) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = estimateObjectIdleTime(o); idle = estimateObjectIdleTime(o);
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { } else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
...@@ -569,6 +569,12 @@ int performEvictions(void) { ...@@ -569,6 +569,12 @@ int performEvictions(void) {
/* Try to smoke-out bugs (server.also_propagate should be empty here) */ /* Try to smoke-out bugs (server.also_propagate should be empty here) */
serverAssert(server.also_propagate.numops == 0); 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) { while (mem_freed < (long long)mem_tofree) {
int j, k, i; int j, k, i;
...@@ -592,12 +598,18 @@ int performEvictions(void) { ...@@ -592,12 +598,18 @@ int performEvictions(void) {
* every DB. */ * every DB. */
for (i = 0; i < server.dbnum; i++) { for (i = 0; i < server.dbnum; i++) {
db = server.db+i; db = server.db+i;
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ? do {
getRandomDict(db) : db->expires; dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
if ((keys = dictSize(dict)) != 0) { getRandomDict(db) : db->expires;
evictionPoolPopulate(i, dict, db, pool); if ((keys = dictSize(dict)) != 0) {
total_keys += keys; 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. */ if (!total_keys) break; /* No keys to evict. */
...@@ -716,6 +728,7 @@ int performEvictions(void) { ...@@ -716,6 +728,7 @@ int performEvictions(void) {
goto cant_free; /* nothing to free... */ 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 */ /* at this point, the memory is OK, or we have reached the time limit */
result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK; result = (isEvictionProcRunning) ? EVICT_RUNNING : EVICT_OK;
......
...@@ -138,6 +138,7 @@ typedef struct redisObject robj; ...@@ -138,6 +138,7 @@ typedef struct redisObject robj;
#define CONFIG_DEFAULT_PROC_TITLE_TEMPLATE "{title} {listen-addr} {server-mode}" #define CONFIG_DEFAULT_PROC_TITLE_TEMPLATE "{title} {listen-addr} {server-mode}"
#define INCREMENTAL_REHASHING_MAX_QUEUE_SIZE (1024*16) #define INCREMENTAL_REHASHING_MAX_QUEUE_SIZE (1024*16)
#define INCREMENTAL_REHASHING_THRESHOLD_MS 1 #define INCREMENTAL_REHASHING_THRESHOLD_MS 1
#define MAX_RANDOM_DICT_PROBE_ATTEMTPS 10
/* Bucket sizes for client eviction pools. Each bucket stores clients with /* Bucket sizes for client eviction pools. Each bucket stores clients with
* memory usage of up to twice the size of the bucket below it. */ * memory usage of up to twice the size of the bucket below it. */
...@@ -874,7 +875,7 @@ struct RedisModuleDigest { ...@@ -874,7 +875,7 @@ struct RedisModuleDigest {
#define LRU_BITS 24 #define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */ #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_SHARED_REFCOUNT INT_MAX /* Global object never destroyed. */
#define OBJ_STATIC_REFCOUNT (INT_MAX-1) /* Object allocated in the stack. */ #define OBJ_STATIC_REFCOUNT (INT_MAX-1) /* Object allocated in the stack. */
...@@ -3024,6 +3025,7 @@ void dismissMemoryInChild(void); ...@@ -3024,6 +3025,7 @@ void dismissMemoryInChild(void);
int restartServer(int flags, mstime_t delay); int restartServer(int flags, mstime_t delay);
unsigned long long int dbSize(redisDb *db); unsigned long long int dbSize(redisDb *db);
dict *getDict(redisDb *db, sds key); dict *getDict(redisDb *db, sds key);
dict *getFairRandomDict(redisDb *db);
dict *getRandomDict(redisDb *db); dict *getRandomDict(redisDb *db);
unsigned long dbSlots(redisDb *db); unsigned long dbSlots(redisDb *db);
void expandDb(const redisDb *db, uint64_t db_size); 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