mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
LFU: Redis object level implementation.
Implementation of LFU maxmemory policy for anything related to Redis objects. Still no actual eviction implemented.
This commit is contained in:
parent
ada70c7c53
commit
5d07984c5d
@ -45,9 +45,11 @@ typedef struct configEnum {
|
|||||||
|
|
||||||
configEnum maxmemory_policy_enum[] = {
|
configEnum maxmemory_policy_enum[] = {
|
||||||
{"volatile-lru", MAXMEMORY_VOLATILE_LRU},
|
{"volatile-lru", MAXMEMORY_VOLATILE_LRU},
|
||||||
|
{"volatile-lfu", MAXMEMORY_VOLATILE_LFU},
|
||||||
{"volatile-random",MAXMEMORY_VOLATILE_RANDOM},
|
{"volatile-random",MAXMEMORY_VOLATILE_RANDOM},
|
||||||
{"volatile-ttl",MAXMEMORY_VOLATILE_TTL},
|
{"volatile-ttl",MAXMEMORY_VOLATILE_TTL},
|
||||||
{"allkeys-lru",MAXMEMORY_ALLKEYS_LRU},
|
{"allkeys-lru",MAXMEMORY_ALLKEYS_LRU},
|
||||||
|
{"allkeys-lfu",MAXMEMORY_ALLKEYS_LFU},
|
||||||
{"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM},
|
{"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM},
|
||||||
{"noeviction",MAXMEMORY_NO_EVICTION},
|
{"noeviction",MAXMEMORY_NO_EVICTION},
|
||||||
{NULL, 0}
|
{NULL, 0}
|
||||||
|
6
src/db.c
6
src/db.c
@ -53,8 +53,14 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
|||||||
server.aof_child_pid == -1 &&
|
server.aof_child_pid == -1 &&
|
||||||
!(flags & LOOKUP_NOTOUCH))
|
!(flags & LOOKUP_NOTOUCH))
|
||||||
{
|
{
|
||||||
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
|
unsigned long ldt = val->lru >> 8;
|
||||||
|
unsigned long counter = LFULogIncr(val->lru & 255);
|
||||||
|
val->lru = (ldt << 8) | counter;
|
||||||
|
} else {
|
||||||
val->lru = LRU_CLOCK();
|
val->lru = LRU_CLOCK();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return val;
|
return val;
|
||||||
} else {
|
} else {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
93
src/evict.c
93
src/evict.c
@ -214,6 +214,99 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* LFU (Least Frequently Used) implementation.
|
||||||
|
|
||||||
|
* We have 24 total bits of space in each object in order to implement
|
||||||
|
* an LFU (Least Frequently Used) eviction policy, since we re-use the
|
||||||
|
* LRU field for this purpose.
|
||||||
|
*
|
||||||
|
* We split the 24 bits into two fields:
|
||||||
|
*
|
||||||
|
* 16 bits 8 bits
|
||||||
|
* +----------------+--------+
|
||||||
|
* + Last decr time | LOG_C |
|
||||||
|
* +----------------+--------+
|
||||||
|
*
|
||||||
|
* LOG_C is a logarithmic counter that provides an indication of the access
|
||||||
|
* frequency. However this field must also be decremented otherwise what used
|
||||||
|
* to be a frequently accessed key in the past, will remain ranked like that
|
||||||
|
* forever, while we want the algorithm to adapt to access pattern changes.
|
||||||
|
*
|
||||||
|
* So the remaining 16 bits are used in order to store the "decrement time",
|
||||||
|
* a reduced-precision Unix time (we take 16 bits of the time converted
|
||||||
|
* in minutes since we don't care about wrapping around) where the LOG_C
|
||||||
|
* counter is halved if it has an high value, or just decremented if it
|
||||||
|
* has a low value.
|
||||||
|
*
|
||||||
|
* New keys don't start at zero, in order to have the ability to collect
|
||||||
|
* some accesses before being trashed away, so they start at COUNTER_INIT_VAL.
|
||||||
|
* The logarithmic increment performed on LOG_C takes care of COUNTER_INIT_VAL
|
||||||
|
* when incrementing the key, so that keys starting at COUNTER_INIT_VAL
|
||||||
|
* (or having a smaller value) have a very high chance of being incremented
|
||||||
|
* on access.
|
||||||
|
*
|
||||||
|
* During decrement, the value of the logarithmic counter is halved if
|
||||||
|
* its current value is greater than two times the COUNTER_INIT_VAL, otherwise
|
||||||
|
* it is just decremented by one.
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* Return the current time in minutes, just taking the least significant
|
||||||
|
* 16 bits. The returned time is suitable to be stored as LDT (last decrement
|
||||||
|
* time) for the LFU implementation. */
|
||||||
|
unsigned long LFUGetTimeInMinutes(void) {
|
||||||
|
return (server.unixtime/60) & 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Given an object last decrement time, compute the minimum number of minutes
|
||||||
|
* that elapsed since the last decrement. Handle overflow (ldt greater than
|
||||||
|
* the current 16 bits minutes time) considering the time as wrapping
|
||||||
|
* exactly once. */
|
||||||
|
unsigned long LFUTimeElapsed(unsigned long ldt) {
|
||||||
|
unsigned long now = LFUGetTimeInMinutes();
|
||||||
|
if (now > ldt) return now-ldt;
|
||||||
|
return 65535-ldt+now;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logarithmically increment a counter. The greater is the current counter value
|
||||||
|
* the less likely is that it gets really implemented. Saturate it at 255. */
|
||||||
|
#define LFU_LOG_FACTOR 10
|
||||||
|
uint8_t LFULogIncr(uint8_t counter) {
|
||||||
|
if (counter == 255) return 255;
|
||||||
|
double r = (double)rand()/RAND_MAX;
|
||||||
|
double baseval = counter - LFU_INIT_VAL;
|
||||||
|
if (baseval < 0) baseval = 0;
|
||||||
|
double p = 1.0/(baseval*LFU_LOG_FACTOR+1);
|
||||||
|
if (r < p) counter++;
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the object decrement time is reached, decrement the LFU counter and
|
||||||
|
* update the decrement time field. Return the object frequency counter.
|
||||||
|
*
|
||||||
|
* This function is used in order to scan the dataset for the best object
|
||||||
|
* to fit: as we check for the candidate, we incrementally decrement the
|
||||||
|
* counter of the scanned objects if needed. */
|
||||||
|
#define LFU_DECR_INTERVAL 1
|
||||||
|
unsigned long LFUDecrAndReturn(robj *o) {
|
||||||
|
unsigned long ldt = o->lru >> 8;
|
||||||
|
unsigned long counter = o->lru & 255;
|
||||||
|
if (LFUTimeElapsed(ldt) > LFU_DECR_INTERVAL && counter) {
|
||||||
|
if (counter > LFU_INIT_VAL*2) {
|
||||||
|
counter /= 2;
|
||||||
|
} else {
|
||||||
|
counter--;
|
||||||
|
}
|
||||||
|
o->lru = (LFUGetTimeInMinutes()<<8) | counter;
|
||||||
|
}
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* The external API for eviction: freeMemroyIfNeeded() is called by the
|
||||||
|
* server when there is data to add in order to make space if needed.
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
int freeMemoryIfNeeded(void) {
|
int freeMemoryIfNeeded(void) {
|
||||||
size_t mem_reported, mem_used, mem_tofree, mem_freed;
|
size_t mem_reported, mem_used, mem_tofree, mem_freed;
|
||||||
int slaves = listLength(server.slaves);
|
int slaves = listLength(server.slaves);
|
||||||
|
20
src/object.c
20
src/object.c
@ -43,8 +43,13 @@ robj *createObject(int type, void *ptr) {
|
|||||||
o->ptr = ptr;
|
o->ptr = ptr;
|
||||||
o->refcount = 1;
|
o->refcount = 1;
|
||||||
|
|
||||||
/* Set the LRU to the current lruclock (minutes resolution). */
|
/* Set the LRU to the current lruclock (minutes resolution), or
|
||||||
|
* alternatively the LFU counter. */
|
||||||
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
|
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
|
||||||
|
} else {
|
||||||
o->lru = LRU_CLOCK();
|
o->lru = LRU_CLOCK();
|
||||||
|
}
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +87,11 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
|
|||||||
o->encoding = OBJ_ENCODING_EMBSTR;
|
o->encoding = OBJ_ENCODING_EMBSTR;
|
||||||
o->ptr = sh+1;
|
o->ptr = sh+1;
|
||||||
o->refcount = 1;
|
o->refcount = 1;
|
||||||
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
|
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
|
||||||
|
} else {
|
||||||
o->lru = LRU_CLOCK();
|
o->lru = LRU_CLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
sh->len = len;
|
sh->len = len;
|
||||||
sh->alloc = len;
|
sh->alloc = len;
|
||||||
@ -394,8 +403,7 @@ robj *tryObjectEncoding(robj *o) {
|
|||||||
* because every object needs to have a private LRU field for the LRU
|
* because every object needs to have a private LRU field for the LRU
|
||||||
* algorithm to work well. */
|
* algorithm to work well. */
|
||||||
if ((server.maxmemory == 0 ||
|
if ((server.maxmemory == 0 ||
|
||||||
(server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
|
!(server.maxmemory & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
|
||||||
server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
|
|
||||||
value >= 0 &&
|
value >= 0 &&
|
||||||
value < OBJ_SHARED_INTEGERS)
|
value < OBJ_SHARED_INTEGERS)
|
||||||
{
|
{
|
||||||
@ -715,8 +723,12 @@ void objectCommand(client *c) {
|
|||||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||||
== NULL) return;
|
== NULL) return;
|
||||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||||
|
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||||
|
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||||
|
== NULL) return;
|
||||||
|
addReplyLongLong(c,o->lru&255);
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
|
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime|freq)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
src/server.h
30
src/server.h
@ -341,13 +341,22 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define SET_OP_DIFF 1
|
#define SET_OP_DIFF 1
|
||||||
#define SET_OP_INTER 2
|
#define SET_OP_INTER 2
|
||||||
|
|
||||||
/* Redis maxmemory strategies */
|
/* Redis maxmemory strategies. Instead of using just incremental number
|
||||||
#define MAXMEMORY_VOLATILE_LRU 0
|
* for this defines, we use a set of flags so that testing for certain
|
||||||
#define MAXMEMORY_VOLATILE_TTL 1
|
* properties common to multiple policies is faster. */
|
||||||
#define MAXMEMORY_VOLATILE_RANDOM 2
|
#define MAXMEMORY_FLAG_LRU (1<<0)
|
||||||
#define MAXMEMORY_ALLKEYS_LRU 3
|
#define MAXMEMORY_FLAG_LFU (1<<1)
|
||||||
#define MAXMEMORY_ALLKEYS_RANDOM 4
|
#define MAXMEMORY_FLAG_NO_SHARED_INTEGERS \
|
||||||
#define MAXMEMORY_NO_EVICTION 5
|
(MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU)
|
||||||
|
#define MAXMEMORY_VOLATILE_LRU ((0<<8)|MAXMEMORY_FLAG_LRU)
|
||||||
|
#define MAXMEMORY_VOLATILE_LFU ((1<<8)|MAXMEMORY_FLAG_LFU)
|
||||||
|
#define MAXMEMORY_VOLATILE_TTL (2<<8)
|
||||||
|
#define MAXMEMORY_VOLATILE_RANDOM (3<<8)
|
||||||
|
#define MAXMEMORY_ALLKEYS_LRU ((4<<8)|MAXMEMORY_FLAG_LRU)
|
||||||
|
#define MAXMEMORY_ALLKEYS_LFU ((5<<8)|MAXMEMORY_FLAG_LFU)
|
||||||
|
#define MAXMEMORY_ALLKEYS_RANDOM (6<<8)
|
||||||
|
#define MAXMEMORY_NO_EVICTION (7<<8)
|
||||||
|
|
||||||
#define CONFIG_DEFAULT_MAXMEMORY_POLICY MAXMEMORY_NO_EVICTION
|
#define CONFIG_DEFAULT_MAXMEMORY_POLICY MAXMEMORY_NO_EVICTION
|
||||||
|
|
||||||
/* Scripting */
|
/* Scripting */
|
||||||
@ -528,7 +537,9 @@ typedef struct RedisModuleIO {
|
|||||||
typedef struct redisObject {
|
typedef struct redisObject {
|
||||||
unsigned type:4;
|
unsigned type:4;
|
||||||
unsigned encoding:4;
|
unsigned encoding:4;
|
||||||
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
|
unsigned lru:LRU_BITS; /* LRU time (relative to server.lruclock) or
|
||||||
|
* LFU data (least significant 8 bits frequency
|
||||||
|
* and most significant 16 bits decreas time). */
|
||||||
int refcount;
|
int refcount;
|
||||||
void *ptr;
|
void *ptr;
|
||||||
} robj;
|
} robj;
|
||||||
@ -1606,6 +1617,9 @@ void activeExpireCycle(int type);
|
|||||||
|
|
||||||
/* evict.c -- maxmemory handling and LRU eviction. */
|
/* evict.c -- maxmemory handling and LRU eviction. */
|
||||||
void evictionPoolAlloc(void);
|
void evictionPoolAlloc(void);
|
||||||
|
#define LFU_INIT_VAL 5
|
||||||
|
unsigned long LFUGetTimeInMinutes(void);
|
||||||
|
uint8_t LFULogIncr(uint8_t value);
|
||||||
|
|
||||||
/* Git SHA1 */
|
/* Git SHA1 */
|
||||||
char *redisGitSHA1(void);
|
char *redisGitSHA1(void);
|
||||||
|
Loading…
Reference in New Issue
Block a user