summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2016-07-15 12:12:52 +0200
committerantirez <antirez@gmail.com>2016-07-15 12:12:58 +0200
commit5d07984c5d48d6253ea5884d69da3f06cdc90f1b (patch)
treec69a4d5043e308dbcf5b8509d2ddc86b85cbcd16
parentada70c7c532f8a1d1fe946dd7663c9e48d834a02 (diff)
downloadredis-5d07984c5d48d6253ea5884d69da3f06cdc90f1b.tar.gz
LFU: Redis object level implementation.
Implementation of LFU maxmemory policy for anything related to Redis objects. Still no actual eviction implemented.
-rw-r--r--src/config.c2
-rw-r--r--src/db.c8
-rw-r--r--src/evict.c93
-rw-r--r--src/object.c24
-rw-r--r--src/server.h30
5 files changed, 142 insertions, 15 deletions
diff --git a/src/config.c b/src/config.c
index 683ec8719..f443ce22a 100644
--- a/src/config.c
+++ b/src/config.c
@@ -45,9 +45,11 @@ typedef struct configEnum {
configEnum maxmemory_policy_enum[] = {
{"volatile-lru", MAXMEMORY_VOLATILE_LRU},
+ {"volatile-lfu", MAXMEMORY_VOLATILE_LFU},
{"volatile-random",MAXMEMORY_VOLATILE_RANDOM},
{"volatile-ttl",MAXMEMORY_VOLATILE_TTL},
{"allkeys-lru",MAXMEMORY_ALLKEYS_LRU},
+ {"allkeys-lfu",MAXMEMORY_ALLKEYS_LFU},
{"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM},
{"noeviction",MAXMEMORY_NO_EVICTION},
{NULL, 0}
diff --git a/src/db.c b/src/db.c
index 03615fd34..b00227d81 100644
--- a/src/db.c
+++ b/src/db.c
@@ -53,7 +53,13 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{
- val->lru = LRU_CLOCK();
+ 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();
+ }
}
return val;
} else {
diff --git a/src/evict.c b/src/evict.c
index 6ce3aef0d..753a2ac81 100644
--- a/src/evict.c
+++ b/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) {
size_t mem_reported, mem_used, mem_tofree, mem_freed;
int slaves = listLength(server.slaves);
diff --git a/src/object.c b/src/object.c
index ec886f1f6..8f19f60f2 100644
--- a/src/object.c
+++ b/src/object.c
@@ -43,8 +43,13 @@ robj *createObject(int type, void *ptr) {
o->ptr = ptr;
o->refcount = 1;
- /* Set the LRU to the current lruclock (minutes resolution). */
- o->lru = LRU_CLOCK();
+ /* 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();
+ }
return o;
}
@@ -82,7 +87,11 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
o->encoding = OBJ_ENCODING_EMBSTR;
o->ptr = sh+1;
o->refcount = 1;
- o->lru = LRU_CLOCK();
+ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
+ o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
+ } else {
+ o->lru = LRU_CLOCK();
+ }
sh->len = 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
* algorithm to work well. */
if ((server.maxmemory == 0 ||
- (server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
- server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
+ !(server.maxmemory & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)
{
@@ -715,8 +723,12 @@ void objectCommand(client *c) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
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 {
- addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
+ addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime|freq)");
}
}
diff --git a/src/server.h b/src/server.h
index 0354877fc..6a9fb2a09 100644
--- a/src/server.h
+++ b/src/server.h
@@ -341,13 +341,22 @@ typedef long long mstime_t; /* millisecond time type. */
#define SET_OP_DIFF 1
#define SET_OP_INTER 2
-/* Redis maxmemory strategies */
-#define MAXMEMORY_VOLATILE_LRU 0
-#define MAXMEMORY_VOLATILE_TTL 1
-#define MAXMEMORY_VOLATILE_RANDOM 2
-#define MAXMEMORY_ALLKEYS_LRU 3
-#define MAXMEMORY_ALLKEYS_RANDOM 4
-#define MAXMEMORY_NO_EVICTION 5
+/* Redis maxmemory strategies. Instead of using just incremental number
+ * for this defines, we use a set of flags so that testing for certain
+ * properties common to multiple policies is faster. */
+#define MAXMEMORY_FLAG_LRU (1<<0)
+#define MAXMEMORY_FLAG_LFU (1<<1)
+#define MAXMEMORY_FLAG_NO_SHARED_INTEGERS \
+ (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
/* Scripting */
@@ -528,7 +537,9 @@ typedef struct RedisModuleIO {
typedef struct redisObject {
unsigned type: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;
void *ptr;
} robj;
@@ -1606,6 +1617,9 @@ void activeExpireCycle(int type);
/* evict.c -- maxmemory handling and LRU eviction. */
void evictionPoolAlloc(void);
+#define LFU_INIT_VAL 5
+unsigned long LFUGetTimeInMinutes(void);
+uint8_t LFULogIncr(uint8_t value);
/* Git SHA1 */
char *redisGitSHA1(void);