Commit e9bb30fd authored by antirez's avatar antirez
Browse files

Experimental: new keyspace and expire algorithm.

This is an alpha quality implementation of a new keyspace representation
and a new expire algorithm for Redis.

This work is described here:

    https://gist.github.com/antirez/b2eb293819666ee104c7fcad71986eb7
parent fd0ee469
...@@ -923,7 +923,7 @@ int rioWriteBulkObject(rio *r, robj *obj) { ...@@ -923,7 +923,7 @@ int rioWriteBulkObject(rio *r, robj *obj) {
/* Emit the commands needed to rebuild a list object. /* Emit the commands needed to rebuild a list object.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteListObject(rio *r, robj *key, robj *o) { int rewriteListObject(rio *r, rkey *key, robj *o) {
long long count = 0, items = listTypeLength(o); long long count = 0, items = listTypeLength(o);
if (o->encoding == OBJ_ENCODING_QUICKLIST) { if (o->encoding == OBJ_ENCODING_QUICKLIST) {
...@@ -937,7 +937,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) { ...@@ -937,7 +937,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
} }
if (entry.value) { if (entry.value) {
...@@ -957,7 +957,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) { ...@@ -957,7 +957,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
/* Emit the commands needed to rebuild a set object. /* Emit the commands needed to rebuild a set object.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteSetObject(rio *r, robj *key, robj *o) { int rewriteSetObject(rio *r, rkey *key, robj *o) {
long long count = 0, items = setTypeSize(o); long long count = 0, items = setTypeSize(o);
if (o->encoding == OBJ_ENCODING_INTSET) { if (o->encoding == OBJ_ENCODING_INTSET) {
...@@ -971,7 +971,7 @@ int rewriteSetObject(rio *r, robj *key, robj *o) { ...@@ -971,7 +971,7 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"SADD",4) == 0) return 0; if (rioWriteBulkString(r,"SADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
} }
if (rioWriteBulkLongLong(r,llval) == 0) return 0; if (rioWriteBulkLongLong(r,llval) == 0) return 0;
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
...@@ -989,7 +989,7 @@ int rewriteSetObject(rio *r, robj *key, robj *o) { ...@@ -989,7 +989,7 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"SADD",4) == 0) return 0; if (rioWriteBulkString(r,"SADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
} }
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0; if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
...@@ -1004,7 +1004,7 @@ int rewriteSetObject(rio *r, robj *key, robj *o) { ...@@ -1004,7 +1004,7 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
/* Emit the commands needed to rebuild a sorted set object. /* Emit the commands needed to rebuild a sorted set object.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteSortedSetObject(rio *r, robj *key, robj *o) { int rewriteSortedSetObject(rio *r, rkey *key, robj *o) {
long long count = 0, items = zsetLength(o); long long count = 0, items = zsetLength(o);
if (o->encoding == OBJ_ENCODING_ZIPLIST) { if (o->encoding == OBJ_ENCODING_ZIPLIST) {
...@@ -1030,7 +1030,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) { ...@@ -1030,7 +1030,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; if (rioWriteBulkString(r,"ZADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
} }
if (rioWriteBulkDouble(r,score) == 0) return 0; if (rioWriteBulkDouble(r,score) == 0) return 0;
if (vstr != NULL) { if (vstr != NULL) {
...@@ -1057,7 +1057,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) { ...@@ -1057,7 +1057,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; if (rioWriteBulkString(r,"ZADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
} }
if (rioWriteBulkDouble(r,*score) == 0) return 0; if (rioWriteBulkDouble(r,*score) == 0) return 0;
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0; if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
...@@ -1099,7 +1099,7 @@ static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) { ...@@ -1099,7 +1099,7 @@ static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
/* Emit the commands needed to rebuild a hash object. /* Emit the commands needed to rebuild a hash object.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteHashObject(rio *r, robj *key, robj *o) { int rewriteHashObject(rio *r, rkey *key, robj *o) {
hashTypeIterator *hi; hashTypeIterator *hi;
long long count = 0, items = hashTypeLength(o); long long count = 0, items = hashTypeLength(o);
...@@ -1111,7 +1111,7 @@ int rewriteHashObject(rio *r, robj *key, robj *o) { ...@@ -1111,7 +1111,7 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
if (rioWriteBulkString(r,"HMSET",5) == 0) return 0; if (rioWriteBulkString(r,"HMSET",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
} }
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) == 0) return 0; if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) == 0) return 0;
...@@ -1140,14 +1140,14 @@ int rioWriteBulkStreamID(rio *r,streamID *id) { ...@@ -1140,14 +1140,14 @@ int rioWriteBulkStreamID(rio *r,streamID *id) {
* add the message described by 'nack' having the id 'rawid', into the pending * add the message described by 'nack' having the id 'rawid', into the pending
* list of the specified consumer. All this in the context of the specified * list of the specified consumer. All this in the context of the specified
* key and group. */ * key and group. */
int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t groupname_len, streamConsumer *consumer, unsigned char *rawid, streamNACK *nack) { int rioWriteStreamPendingEntry(rio *r, rkey *key, const char *groupname, size_t groupname_len, streamConsumer *consumer, unsigned char *rawid, streamNACK *nack) {
/* XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time> /* XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time>
RETRYCOUNT <count> JUSTID FORCE. */ RETRYCOUNT <count> JUSTID FORCE. */
streamID id; streamID id;
streamDecodeID(rawid,&id); streamDecodeID(rawid,&id);
if (rioWriteBulkCount(r,'*',12) == 0) return 0; if (rioWriteBulkCount(r,'*',12) == 0) return 0;
if (rioWriteBulkString(r,"XCLAIM",6) == 0) return 0; if (rioWriteBulkString(r,"XCLAIM",6) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0; if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0;
if (rioWriteBulkString(r,consumer->name,sdslen(consumer->name)) == 0) return 0; if (rioWriteBulkString(r,consumer->name,sdslen(consumer->name)) == 0) return 0;
if (rioWriteBulkString(r,"0",1) == 0) return 0; if (rioWriteBulkString(r,"0",1) == 0) return 0;
...@@ -1163,7 +1163,7 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t ...@@ -1163,7 +1163,7 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t
/* Emit the commands needed to rebuild a stream object. /* Emit the commands needed to rebuild a stream object.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteStreamObject(rio *r, robj *key, robj *o) { int rewriteStreamObject(rio *r, rkey *key, robj *o) {
stream *s = o->ptr; stream *s = o->ptr;
streamIterator si; streamIterator si;
streamIteratorStart(&si,s,NULL,NULL,0); streamIteratorStart(&si,s,NULL,NULL,0);
...@@ -1179,7 +1179,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1179,7 +1179,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
/* Emit the XADD <key> <id> ...fields... command. */ /* Emit the XADD <key> <id> ...fields... command. */
if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0; if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0; if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
if (rioWriteBulkStreamID(r,&id) == 0) return 0; if (rioWriteBulkStreamID(r,&id) == 0) return 0;
while(numfields--) { while(numfields--) {
unsigned char *field, *value; unsigned char *field, *value;
...@@ -1195,7 +1195,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1195,7 +1195,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
* for the Stream type. */ * for the Stream type. */
if (rioWriteBulkCount(r,'*',7) == 0) return 0; if (rioWriteBulkCount(r,'*',7) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0; if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0; if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
if (rioWriteBulkString(r,"0",1) == 0) return 0; if (rioWriteBulkString(r,"0",1) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0; if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
...@@ -1207,7 +1207,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1207,7 +1207,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
* in case of XDEL lastid. */ * in case of XDEL lastid. */
if (rioWriteBulkCount(r,'*',3) == 0) return 0; if (rioWriteBulkCount(r,'*',3) == 0) return 0;
if (rioWriteBulkString(r,"XSETID",6) == 0) return 0; if (rioWriteBulkString(r,"XSETID",6) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0; if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
...@@ -1222,7 +1222,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1222,7 +1222,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkCount(r,'*',5) == 0) return 0; if (rioWriteBulkCount(r,'*',5) == 0) return 0;
if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0; if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0;
if (rioWriteBulkString(r,"CREATE",6) == 0) return 0; if (rioWriteBulkString(r,"CREATE",6) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,key->name,key->len) == 0) return 0;
if (rioWriteBulkString(r,(char*)ri.key,ri.key_len) == 0) return 0; if (rioWriteBulkString(r,(char*)ri.key,ri.key_len) == 0) return 0;
if (rioWriteBulkStreamID(r,&group->last_id) == 0) return 0; if (rioWriteBulkStreamID(r,&group->last_id) == 0) return 0;
...@@ -1262,12 +1262,14 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1262,12 +1262,14 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
/* Call the module type callback in order to rewrite a data type /* Call the module type callback in order to rewrite a data type
* that is exported by a module and is not handled by Redis itself. * that is exported by a module and is not handled by Redis itself.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteModuleObject(rio *r, robj *key, robj *o) { int rewriteModuleObject(rio *r, rkey *key, robj *o) {
RedisModuleIO io; RedisModuleIO io;
moduleValue *mv = o->ptr; moduleValue *mv = o->ptr;
moduleType *mt = mv->type; moduleType *mt = mv->type;
moduleInitIOContext(io,mt,r,key); robj *keyname = createStringObject(key->name,key->len);
mt->aof_rewrite(&io,key,mv->value); moduleInitIOContext(io,mt,r,keyname);
mt->aof_rewrite(&io,keyname,mv->value);
decrRefCount(keyname);
if (io.ctx) { if (io.ctx) {
moduleFreeContext(io.ctx); moduleFreeContext(io.ctx);
zfree(io.ctx); zfree(io.ctx);
...@@ -1309,15 +1311,14 @@ int rewriteAppendOnlyFileRio(rio *aof) { ...@@ -1309,15 +1311,14 @@ int rewriteAppendOnlyFileRio(rio *aof) {
/* Iterate this DB writing every entry */ /* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
sds keystr; rkey *key;
robj key, *o; robj *o;
long long expiretime; long long expiretime;
keystr = dictGetKey(de); key = dictGetKey(de);
o = dictGetVal(de); o = dictGetVal(de);
initStaticStringObject(key,keystr);
expiretime = getExpire(db,&key); expiretime = getExpire(key);
/* Save the key and associated value */ /* Save the key and associated value */
if (o->type == OBJ_STRING) { if (o->type == OBJ_STRING) {
...@@ -1325,20 +1326,21 @@ int rewriteAppendOnlyFileRio(rio *aof) { ...@@ -1325,20 +1326,21 @@ int rewriteAppendOnlyFileRio(rio *aof) {
char cmd[]="*3\r\n$3\r\nSET\r\n"; char cmd[]="*3\r\n$3\r\nSET\r\n";
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr; if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
/* Key and value */ /* Key and value */
if (rioWriteBulkObject(aof,&key) == 0) goto werr; if (rioWriteBulkString(aof,key->name,key->len) == 0)
goto werr;
if (rioWriteBulkObject(aof,o) == 0) goto werr; if (rioWriteBulkObject(aof,o) == 0) goto werr;
} else if (o->type == OBJ_LIST) { } else if (o->type == OBJ_LIST) {
if (rewriteListObject(aof,&key,o) == 0) goto werr; if (rewriteListObject(aof,key,o) == 0) goto werr;
} else if (o->type == OBJ_SET) { } else if (o->type == OBJ_SET) {
if (rewriteSetObject(aof,&key,o) == 0) goto werr; if (rewriteSetObject(aof,key,o) == 0) goto werr;
} else if (o->type == OBJ_ZSET) { } else if (o->type == OBJ_ZSET) {
if (rewriteSortedSetObject(aof,&key,o) == 0) goto werr; if (rewriteSortedSetObject(aof,key,o) == 0) goto werr;
} else if (o->type == OBJ_HASH) { } else if (o->type == OBJ_HASH) {
if (rewriteHashObject(aof,&key,o) == 0) goto werr; if (rewriteHashObject(aof,key,o) == 0) goto werr;
} else if (o->type == OBJ_STREAM) { } else if (o->type == OBJ_STREAM) {
if (rewriteStreamObject(aof,&key,o) == 0) goto werr; if (rewriteStreamObject(aof,key,o) == 0) goto werr;
} else if (o->type == OBJ_MODULE) { } else if (o->type == OBJ_MODULE) {
if (rewriteModuleObject(aof,&key,o) == 0) goto werr; if (rewriteModuleObject(aof,key,o) == 0) goto werr;
} else { } else {
serverPanic("Unknown object type"); serverPanic("Unknown object type");
} }
...@@ -1346,7 +1348,8 @@ int rewriteAppendOnlyFileRio(rio *aof) { ...@@ -1346,7 +1348,8 @@ int rewriteAppendOnlyFileRio(rio *aof) {
if (expiretime != -1) { if (expiretime != -1) {
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n"; char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr; if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
if (rioWriteBulkObject(aof,&key) == 0) goto werr; if (rioWriteBulkString(aof,key->name,key->len) == 0)
goto werr;
if (rioWriteBulkLongLong(aof,expiretime) == 0) goto werr; if (rioWriteBulkLongLong(aof,expiretime) == 0) goto werr;
} }
/* Read some diff from the parent process from time to time. */ /* Read some diff from the parent process from time to time. */
......
...@@ -85,7 +85,7 @@ struct bio_job { ...@@ -85,7 +85,7 @@ struct bio_job {
void *bioProcessBackgroundJobs(void *arg); void *bioProcessBackgroundJobs(void *arg);
void lazyfreeFreeObjectFromBioThread(robj *o); void lazyfreeFreeObjectFromBioThread(robj *o);
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2); void lazyfreeFreeDatabaseFromBioThread(dict *ht, rax *tree);
void lazyfreeFreeSlotsMapFromBioThread(zskiplist *sl); void lazyfreeFreeSlotsMapFromBioThread(zskiplist *sl);
/* Make sure we have enough stack to perform all the things we do in the /* Make sure we have enough stack to perform all the things we do in the
......
...@@ -477,7 +477,7 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) { ...@@ -477,7 +477,7 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
* an error is sent to the client. */ * an error is sent to the client. */
robj *lookupStringForBitCommand(client *c, size_t maxbit) { robj *lookupStringForBitCommand(client *c, size_t maxbit) {
size_t byte = maxbit >> 3; size_t byte = maxbit >> 3;
robj *o = lookupKeyWrite(c->db,c->argv[1]); robj *o = lookupKeyWrite(c->db,c->argv[1],NULL);
if (o == NULL) { if (o == NULL) {
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1)); o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
...@@ -571,7 +571,7 @@ void getbitCommand(client *c) { ...@@ -571,7 +571,7 @@ void getbitCommand(client *c) {
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK) if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
return; return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || if ((o = lookupKeyReadOrReply(c,c->argv[1],NULL,shared.czero)) == NULL ||
checkType(c,o,OBJ_STRING)) return; checkType(c,o,OBJ_STRING)) return;
byte = bitoffset >> 3; byte = bitoffset >> 3;
...@@ -625,7 +625,7 @@ void bitopCommand(client *c) { ...@@ -625,7 +625,7 @@ void bitopCommand(client *c) {
len = zmalloc(sizeof(long) * numkeys); len = zmalloc(sizeof(long) * numkeys);
objects = zmalloc(sizeof(robj*) * numkeys); objects = zmalloc(sizeof(robj*) * numkeys);
for (j = 0; j < numkeys; j++) { for (j = 0; j < numkeys; j++) {
o = lookupKeyRead(c->db,c->argv[j+3]); o = lookupKeyRead(c->db,c->argv[j+3],NULL);
/* Handle non-existing keys as empty strings. */ /* Handle non-existing keys as empty strings. */
if (o == NULL) { if (o == NULL) {
objects[j] = NULL; objects[j] = NULL;
...@@ -773,7 +773,7 @@ void bitcountCommand(client *c) { ...@@ -773,7 +773,7 @@ void bitcountCommand(client *c) {
char llbuf[LONG_STR_SIZE]; char llbuf[LONG_STR_SIZE];
/* Lookup, check for type, and return 0 for non existing keys. */ /* Lookup, check for type, and return 0 for non existing keys. */
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || if ((o = lookupKeyReadOrReply(c,c->argv[1],NULL,shared.czero)) == NULL ||
checkType(c,o,OBJ_STRING)) return; checkType(c,o,OBJ_STRING)) return;
p = getObjectReadOnlyString(o,&strlen,llbuf); p = getObjectReadOnlyString(o,&strlen,llbuf);
...@@ -834,7 +834,7 @@ void bitposCommand(client *c) { ...@@ -834,7 +834,7 @@ void bitposCommand(client *c) {
/* If the key does not exist, from our point of view it is an infinite /* If the key does not exist, from our point of view it is an infinite
* array of 0 bits. If the user is looking for the fist clear bit return 0, * array of 0 bits. If the user is looking for the fist clear bit return 0,
* If the user is looking for the first set bit, return -1. */ * If the user is looking for the first set bit, return -1. */
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { if ((o = lookupKeyRead(c->db,c->argv[1],NULL)) == NULL) {
addReplyLongLong(c, bit ? -1 : 0); addReplyLongLong(c, bit ? -1 : 0);
return; return;
} }
...@@ -993,7 +993,7 @@ void bitfieldCommand(client *c) { ...@@ -993,7 +993,7 @@ void bitfieldCommand(client *c) {
if (readonly) { if (readonly) {
/* Lookup for read is ok if key doesn't exit, but errors /* Lookup for read is ok if key doesn't exit, but errors
* if it's not a string. */ * if it's not a string. */
o = lookupKeyRead(c->db,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1],NULL);
if (o != NULL && checkType(c,o,OBJ_STRING)) { if (o != NULL && checkType(c,o,OBJ_STRING)) {
zfree(ops); zfree(ops);
return; return;
......
...@@ -270,7 +270,7 @@ void handleClientsBlockedOnKeys(void) { ...@@ -270,7 +270,7 @@ void handleClientsBlockedOnKeys(void) {
dictDelete(rl->db->ready_keys,rl->key); dictDelete(rl->db->ready_keys,rl->key);
/* Serve clients blocked on list key. */ /* Serve clients blocked on list key. */
robj *o = lookupKeyWrite(rl->db,rl->key); robj *o = lookupKeyWrite(rl->db,rl->key,NULL);
if (o != NULL && o->type == OBJ_LIST) { if (o != NULL && o->type == OBJ_LIST) {
dictEntry *de; dictEntry *de;
......
...@@ -4776,7 +4776,7 @@ NULL ...@@ -4776,7 +4776,7 @@ NULL
/* Generates a DUMP-format representation of the object 'o', adding it to the /* Generates a DUMP-format representation of the object 'o', adding it to the
* io stream pointed by 'rio'. This function can't fail. */ * io stream pointed by 'rio'. This function can't fail. */
void createDumpPayload(rio *payload, robj *o, robj *key) { void createDumpPayload(rio *payload, robj *o, rkey *key) {
unsigned char buf[2]; unsigned char buf[2];
uint64_t crc; uint64_t crc;
...@@ -4833,16 +4833,17 @@ int verifyDumpPayload(unsigned char *p, size_t len) { ...@@ -4833,16 +4833,17 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
* complement of RESTORE and can be useful for different applications. */ * complement of RESTORE and can be useful for different applications. */
void dumpCommand(client *c) { void dumpCommand(client *c) {
robj *o, *dumpobj; robj *o, *dumpobj;
rkey *key;
rio payload; rio payload;
/* Check if the key is here. */ /* Check if the key is here. */
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { if ((o = lookupKeyRead(c->db,c->argv[1],&key)) == NULL) {
addReplyNull(c); addReplyNull(c);
return; return;
} }
/* Create the DUMP encoded representation. */ /* Create the DUMP encoded representation. */
createDumpPayload(&payload,o,c->argv[1]); createDumpPayload(&payload,o,key);
/* Transfer to the client */ /* Transfer to the client */
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr); dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
...@@ -4893,7 +4894,7 @@ void restoreCommand(client *c) { ...@@ -4893,7 +4894,7 @@ void restoreCommand(client *c) {
} }
/* Make sure this key does not already exist here... */ /* Make sure this key does not already exist here... */
if (!replace && lookupKeyWrite(c->db,c->argv[1]) != NULL) { if (!replace && lookupKeyWrite(c->db,c->argv[1],NULL) != NULL) {
addReply(c,shared.busykeyerr); addReply(c,shared.busykeyerr);
return; return;
} }
...@@ -4925,12 +4926,12 @@ void restoreCommand(client *c) { ...@@ -4925,12 +4926,12 @@ void restoreCommand(client *c) {
if (replace) dbDelete(c->db,c->argv[1]); if (replace) dbDelete(c->db,c->argv[1]);
/* Create the key and set the TTL if any */ /* Create the key and set the TTL if any */
dbAdd(c->db,c->argv[1],obj); rkey *key = dbAdd(c->db,c->argv[1],obj);
if (ttl) { if (ttl) {
if (!absttl) ttl+=mstime(); if (!absttl) ttl+=mstime();
setExpire(c,c->db,c->argv[1],ttl); setExpire(c,c->db,key,ttl);
} }
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock); objectSetLRUOrLFU(key,lfu_freq,lru_idle,lru_clock);
signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]);
addReply(c,shared.ok); addReply(c,shared.ok);
server.dirty++; server.dirty++;
...@@ -5066,7 +5067,8 @@ void migrateCommand(client *c) { ...@@ -5066,7 +5067,8 @@ void migrateCommand(client *c) {
long timeout; long timeout;
long dbid; long dbid;
robj **ov = NULL; /* Objects to migrate. */ robj **ov = NULL; /* Objects to migrate. */
robj **kv = NULL; /* Key names. */ robj **knv = NULL; /* Key names. */
rkey **kov = NULL; /* Key objects. */
robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */ robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */
rio cmd, payload; rio cmd, payload;
int may_retry = 1; int may_retry = 1;
...@@ -5121,18 +5123,21 @@ void migrateCommand(client *c) { ...@@ -5121,18 +5123,21 @@ void migrateCommand(client *c) {
* this case, since often this is due to a normal condition like the key * this case, since often this is due to a normal condition like the key
* expiring in the meantime. */ * expiring in the meantime. */
ov = zrealloc(ov,sizeof(robj*)*num_keys); ov = zrealloc(ov,sizeof(robj*)*num_keys);
kv = zrealloc(kv,sizeof(robj*)*num_keys); knv = zrealloc(knv,sizeof(robj*)*num_keys);
kov = zrealloc(kov,sizeof(rkey*)*num_keys);
int oi = 0; int oi = 0;
for (j = 0; j < num_keys; j++) { for (j = 0; j < num_keys; j++) {
if ((ov[oi] = lookupKeyRead(c->db,c->argv[first_key+j])) != NULL) { rkey *ko;
kv[oi] = c->argv[first_key+j]; if ((ov[oi] = lookupKeyRead(c->db,c->argv[first_key+j],&ko)) != NULL) {
knv[oi] = c->argv[first_key+j];
kov[oi] = ko;
oi++; oi++;
} }
} }
num_keys = oi; num_keys = oi;
if (num_keys == 0) { if (num_keys == 0) {
zfree(ov); zfree(kv); zfree(ov); zfree(knv); zfree(kov);
addReplySds(c,sdsnew("+NOKEY\r\n")); addReplySds(c,sdsnew("+NOKEY\r\n"));
return; return;
} }
...@@ -5143,7 +5148,7 @@ try_again: ...@@ -5143,7 +5148,7 @@ try_again:
/* Connect */ /* Connect */
cs = migrateGetSocket(c,c->argv[1],c->argv[2],timeout); cs = migrateGetSocket(c,c->argv[1],c->argv[2],timeout);
if (cs == NULL) { if (cs == NULL) {
zfree(ov); zfree(kv); zfree(ov); zfree(knv); zfree(kov);
return; /* error sent to the client by migrateGetSocket() */ return; /* error sent to the client by migrateGetSocket() */
} }
...@@ -5173,7 +5178,7 @@ try_again: ...@@ -5173,7 +5178,7 @@ try_again:
/* Create RESTORE payload and generate the protocol to call the command. */ /* Create RESTORE payload and generate the protocol to call the command. */
for (j = 0; j < num_keys; j++) { for (j = 0; j < num_keys; j++) {
long long ttl = 0; long long ttl = 0;
long long expireat = getExpire(c->db,kv[j]); long long expireat = getExpire(kov[j]);
if (expireat != -1) { if (expireat != -1) {
ttl = expireat-mstime(); ttl = expireat-mstime();
...@@ -5186,7 +5191,9 @@ try_again: ...@@ -5186,7 +5191,9 @@ try_again:
/* Relocate valid (non expired) keys into the array in successive /* Relocate valid (non expired) keys into the array in successive
* positions to remove holes created by the keys that were present * positions to remove holes created by the keys that were present
* in the first lookup but are now expired after the second lookup. */ * in the first lookup but are now expired after the second lookup. */
kv[non_expired++] = kv[j]; knv[non_expired] = knv[j];
kov[non_expired] = kov[j];
non_expired++;
serverAssertWithInfo(c,NULL, serverAssertWithInfo(c,NULL,
rioWriteBulkCount(&cmd,'*',replace ? 5 : 4)); rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
...@@ -5196,14 +5203,14 @@ try_again: ...@@ -5196,14 +5203,14 @@ try_again:
rioWriteBulkString(&cmd,"RESTORE-ASKING",14)); rioWriteBulkString(&cmd,"RESTORE-ASKING",14));
else else
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7)); serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7));
serverAssertWithInfo(c,NULL,sdsEncodedObject(kv[j])); serverAssertWithInfo(c,NULL,sdsEncodedObject(knv[j]));
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,kv[j]->ptr, serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,knv[j]->ptr,
sdslen(kv[j]->ptr))); sdslen(knv[j]->ptr)));
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl)); serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl));
/* Emit the payload argument, that is the serialized object using /* Emit the payload argument, that is the serialized object using
* the DUMP format. */ * the DUMP format. */
createDumpPayload(&payload,ov[j],kv[j]); createDumpPayload(&payload,ov[j],kov[j]);
serverAssertWithInfo(c,NULL, serverAssertWithInfo(c,NULL,
rioWriteBulkString(&cmd,payload.io.buffer.ptr, rioWriteBulkString(&cmd,payload.io.buffer.ptr,
sdslen(payload.io.buffer.ptr))); sdslen(payload.io.buffer.ptr)));
...@@ -5283,13 +5290,13 @@ try_again: ...@@ -5283,13 +5290,13 @@ try_again:
} else { } else {
if (!copy) { if (!copy) {
/* No COPY option: remove the local key, signal the change. */ /* No COPY option: remove the local key, signal the change. */
dbDelete(c->db,kv[j]); dbDelete(c->db,knv[j]);
signalModifiedKey(c->db,kv[j]); signalModifiedKey(c->db,knv[j]);
server.dirty++; server.dirty++;
/* Populate the argument vector to replace the old one. */ /* Populate the argument vector to replace the old one. */
newargv[del_idx++] = kv[j]; newargv[del_idx++] = knv[j];
incrRefCount(kv[j]); incrRefCount(knv[j]);
} }
} }
} }
...@@ -5347,7 +5354,7 @@ try_again: ...@@ -5347,7 +5354,7 @@ try_again:
} }
sdsfree(cmd.io.buffer.ptr); sdsfree(cmd.io.buffer.ptr);
zfree(ov); zfree(kv); zfree(newargv); zfree(ov); zfree(knv); zfree(kov); zfree(newargv);
return; return;
/* On socket errors we try to close the cached socket and try again. /* On socket errors we try to close the cached socket and try again.
...@@ -5374,7 +5381,7 @@ socket_err: ...@@ -5374,7 +5381,7 @@ socket_err:
} }
/* Cleanup we want to do if no retry is attempted. */ /* Cleanup we want to do if no retry is attempted. */
zfree(ov); zfree(kv); zfree(ov); zfree(knv); zfree(kov);
addReplySds(c, addReplySds(c,
sdscatprintf(sdsempty(), sdscatprintf(sdsempty(),
"-IOERR error or timeout %s to target instance\r\n", "-IOERR error or timeout %s to target instance\r\n",
...@@ -5554,7 +5561,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in ...@@ -5554,7 +5561,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
/* Migarting / Improrting slot? Count keys we don't have. */ /* Migarting / Improrting slot? Count keys we don't have. */
if ((migrating_slot || importing_slot) && if ((migrating_slot || importing_slot) &&
lookupKeyRead(&server.db[0],thiskey) == NULL) lookupKeyRead(&server.db[0],thiskey,NULL) == NULL)
{ {
missing_keys++; missing_keys++;
} }
......
...@@ -1650,19 +1650,23 @@ void dictListDestructor(void *privdata, void *val); ...@@ -1650,19 +1650,23 @@ void dictListDestructor(void *privdata, void *val);
void rewriteConfigSentinelOption(struct rewriteConfigState *state); void rewriteConfigSentinelOption(struct rewriteConfigState *state);
dictType optionToLineDictType = { dictType optionToLineDictType = {
dictSdsCaseHash, /* hash function */ dictSdsCaseHash, /* lookup hash function */
dictSdsCaseHash, /* store hash function */
NULL, /* key dup */ NULL, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */ dictSdsKeyCaseCompare, /* lookup key compare */
dictSdsKeyCaseCompare, /* stored key compare */
dictSdsDestructor, /* key destructor */ dictSdsDestructor, /* key destructor */
dictListDestructor /* val destructor */ dictListDestructor /* val destructor */
}; };
dictType optionSetDictType = { dictType optionSetDictType = {
dictSdsCaseHash, /* hash function */ dictSdsCaseHash, /* lookup hash function */
dictSdsCaseHash, /* store hash function */
NULL, /* key dup */ NULL, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */ dictSdsKeyCaseCompare, /* lookup key compare */
dictSdsKeyCaseCompare, /* stored key compare */
dictSdsDestructor, /* key destructor */ dictSdsDestructor, /* key destructor */
NULL /* val destructor */ NULL /* val destructor */
}; };
......
...@@ -38,24 +38,26 @@ ...@@ -38,24 +38,26 @@
* C-level DB API * C-level DB API
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
int keyIsExpired(redisDb *db, robj *key); int keyIsExpired(rkey *key);
/* Update LFU when an object is accessed. /* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached. * Firstly, decrement the counter if the decrement time is reached.
* Then logarithmically increment the counter, and update the access time. */ * Then logarithmically increment the counter, and update the access time. */
void updateLFU(robj *val) { void updateLFU(rkey *key) {
unsigned long counter = LFUDecrAndReturn(val); unsigned long counter = LFUDecrAndReturn(key);
counter = LFULogIncr(counter); counter = LFULogIncr(counter);
val->lru = (LFUGetTimeInMinutes()<<8) | counter; key->lru = (LFUGetTimeInMinutes()<<8) | counter;
} }
/* Low level key lookup API, not actually called directly from commands /* Low level key lookup API, not actually called directly from commands
* implementations that should instead rely on lookupKeyRead(), * implementations that should instead rely on lookupKeyRead(),
* lookupKeyWrite() and lookupKeyReadWithFlags(). */ * lookupKeyWrite() and lookupKeyReadWithFlags(). */
robj *lookupKey(redisDb *db, robj *key, int flags) { robj *lookupKey(redisDb *db, robj *keyname, rkey **keyptr, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr); dictEntry *de = dictFind(db->dict,keyname->ptr);
if (de) { if (de) {
rkey *key = dictGetKey(de);
robj *val = dictGetVal(de); robj *val = dictGetVal(de);
if (keyptr) *keyptr = key;
/* Update the access time for the ageing algorithm. /* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger * Don't do it if we have a saving child, as this will trigger
...@@ -65,9 +67,9 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { ...@@ -65,9 +67,9 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
!(flags & LOOKUP_NOTOUCH)) !(flags & LOOKUP_NOTOUCH))
{ {
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val); updateLFU(key);
} else { } else {
val->lru = LRU_CLOCK(); key->lru = LRU_CLOCK();
} }
} }
return val; return val;
...@@ -98,16 +100,18 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { ...@@ -98,16 +100,18 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* for read operations. Even if the key expiry is master-driven, we can * for read operations. Even if the key expiry is master-driven, we can
* correctly report a key is expired on slaves even if the master is lagging * correctly report a key is expired on slaves even if the master is lagging
* expiring our key via DELs in the replication link. */ * expiring our key via DELs in the replication link. */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { robj *lookupKeyReadWithFlags(redisDb *db, robj *keyname, rkey **keyptr, int flags) {
robj *val; robj *val;
rkey *keyobj;
if (expireIfNeeded(db,key) == 1) { val = lookupKey(db,keyname,&keyobj,flags);
if (val && expireIfNeeded(db,keyname,keyobj) == 1) {
/* Key expired. If we are in the context of a master, expireIfNeeded() /* Key expired. If we are in the context of a master, expireIfNeeded()
* returns 0 only when the key does not exist at all, so it's safe * returns 0 only when the key does not exist at all, so it's safe
* to return NULL ASAP. */ * to return NULL ASAP. */
if (server.masterhost == NULL) { if (server.masterhost == NULL) {
server.stat_keyspace_misses++; server.stat_keyspace_misses++;
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id); notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", keyname, db->id);
return NULL; return NULL;
} }
...@@ -129,24 +133,24 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { ...@@ -129,24 +133,24 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
server.current_client->cmd->flags & CMD_READONLY) server.current_client->cmd->flags & CMD_READONLY)
{ {
server.stat_keyspace_misses++; server.stat_keyspace_misses++;
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id); notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", keyname, db->id);
return NULL; return NULL;
} }
} }
val = lookupKey(db,key,flags);
if (val == NULL) { if (val == NULL) {
server.stat_keyspace_misses++; server.stat_keyspace_misses++;
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id); notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", keyname, db->id);
} }
else else
server.stat_keyspace_hits++; server.stat_keyspace_hits++;
if (keyptr) *keyptr = keyobj;
return val; return val;
} }
/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the /* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
* common case. */ * common case. */
robj *lookupKeyRead(redisDb *db, robj *key) { robj *lookupKeyRead(redisDb *db, robj *keyname, rkey **keyptr) {
return lookupKeyReadWithFlags(db,key,LOOKUP_NONE); return lookupKeyReadWithFlags(db,keyname,keyptr,LOOKUP_NONE);
} }
/* Lookup a key for write operations, and as a side effect, if needed, expires /* Lookup a key for write operations, and as a side effect, if needed, expires
...@@ -154,36 +158,64 @@ robj *lookupKeyRead(redisDb *db, robj *key) { ...@@ -154,36 +158,64 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
* *
* Returns the linked value object if the key exists or NULL if the key * Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */ * does not exist in the specified DB. */
robj *lookupKeyWrite(redisDb *db, robj *key) { robj *lookupKeyWrite(redisDb *db, robj *keyname, rkey **keyptr) {
expireIfNeeded(db,key); rkey *key;
return lookupKey(db,key,LOOKUP_NONE); robj *val = lookupKey(db,keyname,&key,LOOKUP_NONE);
if (val && expireIfNeeded(db,keyname,key)) return NULL;
if (keyptr) *keyptr = key;
return val;
} }
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) { robj *lookupKeyReadOrReply(client *c, robj *keyname, rkey **keyptr, robj *reply) {
robj *o = lookupKeyRead(c->db, key); robj *o = lookupKeyRead(c->db, keyname, keyptr);
if (!o) addReply(c,reply); if (!o) addReply(c,reply);
return o; return o;
} }
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) { robj *lookupKeyWriteOrReply(client *c, robj *keyname, rkey **keyptr, robj *reply) {
robj *o = lookupKeyWrite(c->db, key); robj *o = lookupKeyWrite(c->db, keyname, keyptr);
if (!o) addReply(c,reply); if (!o) addReply(c,reply);
return o; return o;
} }
/* Allocate and return a key object. Key objects are used to represent
* keys in the main Redis dictionaries that associate key names to
* value objects. */
rkey *createKey(const char *keyname, size_t keylen) {
rkey *k = zcalloc(sizeof(*k) + keylen + 1);
k->len = keylen;
memcpy(k->name,keyname,keylen);
k->name[keylen] = '\0';
return k;
}
void freeKey(rkey *key) {
zfree(key);
}
/* Add the key to the DB. It's up to the caller to increment the reference /* Add the key to the DB. It's up to the caller to increment the reference
* counter of the value if needed. * counter of the value if needed.
* *
* The program is aborted if the key already exists. */ * The program is aborted if the key already exists. */
void dbAdd(redisDb *db, robj *key, robj *val) { rkey *dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr); rkey *ko = createKey(key->ptr,sdslen(key->ptr));
int retval = dictAdd(db->dict, copy, val); int retval = dictAdd(db->dict, ko, val);
serverAssertWithInfo(NULL,key,retval == DICT_OK); serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST || if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET) val->type == OBJ_ZSET)
signalKeyAsReady(db, key); signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key); if (server.cluster_enabled) slotToKeyAdd(key);
/* Set the LRU to the current lruclock (minutes resolution), or
* alternatively the LFU counter. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
ko->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
ko->lru = LRU_CLOCK();
}
return ko;
} }
/* Overwrite an existing key with a new value. Incrementing the reference /* Overwrite an existing key with a new value. Incrementing the reference
...@@ -191,15 +223,13 @@ void dbAdd(redisDb *db, robj *key, robj *val) { ...@@ -191,15 +223,13 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
* This function does not modify the expire time of the existing key. * This function does not modify the expire time of the existing key.
* *
* The program is aborted if the key was not already present. */ * The program is aborted if the key was not already present. */
void dbOverwrite(redisDb *db, robj *key, robj *val) { rkey *dbOverwrite(redisDb *db, robj *keyname, robj *val) {
dictEntry *de = dictFind(db->dict,key->ptr); dictEntry *de = dictFind(db->dict,keyname->ptr);
serverAssertWithInfo(NULL,key,de != NULL); serverAssertWithInfo(NULL,keyname,de != NULL);
dictEntry auxentry = *de; dictEntry auxentry = *de;
robj *old = dictGetVal(de); robj *old = dictGetVal(de);
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { rkey *key = dictGetKey(de);
val->lru = old->lru;
}
dictSetVal(db->dict, de, val); dictSetVal(db->dict, de, val);
if (server.lazyfree_lazy_server_del) { if (server.lazyfree_lazy_server_del) {
...@@ -208,6 +238,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) { ...@@ -208,6 +238,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
} }
dictFreeVal(db->dict, &auxentry); dictFreeVal(db->dict, &auxentry);
return key;
} }
/* High level Set operation. This function can be used in order to set /* High level Set operation. This function can be used in order to set
...@@ -217,16 +248,20 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) { ...@@ -217,16 +248,20 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
* 2) clients WATCHing for the destination key notified. * 2) clients WATCHing for the destination key notified.
* 3) The expire time of the key is reset (the key is made persistent). * 3) The expire time of the key is reset (the key is made persistent).
* *
* All the new keys in the database should be created via this interface. */ * All the new keys in the database should be created via this interface.
void setKey(redisDb *db, robj *key, robj *val) { * The function returns a pointer to the key object representing the key
if (lookupKeyWrite(db,key) == NULL) { * in the database dictionary. */
dbAdd(db,key,val); rkey *setKey(redisDb *db, robj *keyname, robj *val) {
rkey *key;
if (lookupKeyWrite(db,keyname,&key) == NULL) {
key = dbAdd(db,keyname,val);
} else { } else {
dbOverwrite(db,key,val); key = dbOverwrite(db,keyname,val);
} }
incrRefCount(val); incrRefCount(val);
removeExpire(db,key); removeExpire(db,key);
signalModifiedKey(db,key); signalModifiedKey(db,keyname);
return key;
} }
int dbExists(redisDb *db, robj *key) { int dbExists(redisDb *db, robj *key) {
...@@ -240,18 +275,18 @@ int dbExists(redisDb *db, robj *key) { ...@@ -240,18 +275,18 @@ int dbExists(redisDb *db, robj *key) {
robj *dbRandomKey(redisDb *db) { robj *dbRandomKey(redisDb *db) {
dictEntry *de; dictEntry *de;
int maxtries = 100; int maxtries = 100;
int allvolatile = dictSize(db->dict) == dictSize(db->expires); int allvolatile = dictSize(db->dict) == raxSize(db->expires);
while(1) { while(1) {
sds key; rkey *key;
robj *keyobj; robj *keyname;
de = dictGetFairRandomKey(db->dict); de = dictGetFairRandomKey(db->dict);
if (de == NULL) return NULL; if (de == NULL) return NULL;
key = dictGetKey(de); key = dictGetKey(de);
keyobj = createStringObject(key,sdslen(key)); keyname = createStringObject(key->name,key->len);
if (dictFind(db->expires,key)) { if (key->flags & KEY_FLAG_EXPIRE) {
if (allvolatile && server.masterhost && --maxtries == 0) { if (allvolatile && server.masterhost && --maxtries == 0) {
/* If the DB is composed only of keys with an expire set, /* If the DB is composed only of keys with an expire set,
* it could happen that all the keys are already logically * it could happen that all the keys are already logically
...@@ -261,28 +296,27 @@ robj *dbRandomKey(redisDb *db) { ...@@ -261,28 +296,27 @@ robj *dbRandomKey(redisDb *db) {
* To prevent the infinite loop we do some tries, but if there * To prevent the infinite loop we do some tries, but if there
* are the conditions for an infinite loop, eventually we * are the conditions for an infinite loop, eventually we
* return a key name that may be already expired. */ * return a key name that may be already expired. */
return keyobj; return keyname;
} }
if (expireIfNeeded(db,keyobj)) { if (expireIfNeeded(db,keyname,key)) {
decrRefCount(keyobj); decrRefCount(keyname);
continue; /* search for another key. This expired. */ continue; /* search for another key. This expired. */
} }
} }
return keyobj; return keyname;
} }
} }
/* Delete a key, value, and associated expiration entry if any, from the DB */ /* Delete a key, value, and associated expiration entry if any, from the DB.
int dbSyncDelete(redisDb *db, robj *key) { * The function returns 1 is they existed and was removed, 0 otherwise. */
/* Deleting an entry from the expires dict will not free the sds of int dbSyncDelete(redisDb *db, robj *keyname) {
* the key, because it is shared with the main dictionary. */ dictEntry *de = dictUnlink(db->dict,keyname->ptr);
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); if (de == NULL) return 0;
if (dictDelete(db->dict,key->ptr) == DICT_OK) { rkey *key = dictGetKey(de);
if (server.cluster_enabled) slotToKeyDel(key); if (key->flags & KEY_FLAG_EXPIRE) removeExpireFromTree(db,key);
return 1; if (server.cluster_enabled) slotToKeyDel(keyname);
} else { dictFreeUnlinkedEntry(db->dict,de);
return 0; return 1;
}
} }
/* This is a wrapper whose behavior depends on the Redis lazy free /* This is a wrapper whose behavior depends on the Redis lazy free
...@@ -312,7 +346,7 @@ int dbDelete(redisDb *db, robj *key) { ...@@ -312,7 +346,7 @@ int dbDelete(redisDb *db, robj *key) {
* The object 'o' is what the caller already obtained by looking up 'key' * The object 'o' is what the caller already obtained by looking up 'key'
* in 'db', the usage pattern looks like this: * in 'db', the usage pattern looks like this:
* *
* o = lookupKeyWrite(db,key); * o = lookupKeyWrite(db,key,NULL);
* if (checkType(c,o,OBJ_STRING)) return; * if (checkType(c,o,OBJ_STRING)) return;
* o = dbUnshareStringValue(db,key,o); * o = dbUnshareStringValue(db,key,o);
* *
...@@ -367,7 +401,7 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) { ...@@ -367,7 +401,7 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
emptyDbAsync(&server.db[j]); emptyDbAsync(&server.db[j]);
} else { } else {
dictEmpty(server.db[j].dict,callback); dictEmpty(server.db[j].dict,callback);
dictEmpty(server.db[j].expires,callback); raxFreeWithCallback(server.db[j].expires,callback);
} }
} }
if (server.cluster_enabled) { if (server.cluster_enabled) {
...@@ -471,7 +505,7 @@ void delGenericCommand(client *c, int lazy) { ...@@ -471,7 +505,7 @@ void delGenericCommand(client *c, int lazy) {
int numdel = 0, j; int numdel = 0, j;
for (j = 1; j < c->argc; j++) { for (j = 1; j < c->argc; j++) {
expireIfNeeded(c->db,c->argv[j]); expireIfNeededByName(c->db,c->argv[j]);
int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) : int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
dbSyncDelete(c->db,c->argv[j]); dbSyncDelete(c->db,c->argv[j]);
if (deleted) { if (deleted) {
...@@ -500,7 +534,7 @@ void existsCommand(client *c) { ...@@ -500,7 +534,7 @@ void existsCommand(client *c) {
int j; int j;
for (j = 1; j < c->argc; j++) { for (j = 1; j < c->argc; j++) {
if (lookupKeyRead(c->db,c->argv[j])) count++; if (lookupKeyRead(c->db,c->argv[j],NULL)) count++;
} }
addReplyLongLong(c,count); addReplyLongLong(c,count);
} }
...@@ -546,16 +580,13 @@ void keysCommand(client *c) { ...@@ -546,16 +580,13 @@ void keysCommand(client *c) {
di = dictGetSafeIterator(c->db->dict); di = dictGetSafeIterator(c->db->dict);
allkeys = (pattern[0] == '*' && pattern[1] == '\0'); allkeys = (pattern[0] == '*' && pattern[1] == '\0');
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
sds key = dictGetKey(de); rkey *key = dictGetKey(de);
robj *keyobj;
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { if (allkeys || stringmatchlen(pattern,plen,key->name,key->len,0)) {
keyobj = createStringObject(key,sdslen(key)); if (!keyIsExpired(key)) {
if (!keyIsExpired(c->db,keyobj)) { addReplyBulkCBuffer(c,key->name,key->len);
addReplyBulk(c,keyobj);
numkeys++; numkeys++;
} }
decrRefCount(keyobj);
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
...@@ -571,8 +602,8 @@ void scanCallback(void *privdata, const dictEntry *de) { ...@@ -571,8 +602,8 @@ void scanCallback(void *privdata, const dictEntry *de) {
robj *key, *val = NULL; robj *key, *val = NULL;
if (o == NULL) { if (o == NULL) {
sds sdskey = dictGetKey(de); rkey *ko = dictGetKey(de);
key = createStringObject(sdskey, sdslen(sdskey)); key = createStringObject(ko->name, ko->len);
} else if (o->type == OBJ_SET) { } else if (o->type == OBJ_SET) {
sds keysds = dictGetKey(de); sds keysds = dictGetKey(de);
key = createStringObject(keysds,sdslen(keysds)); key = createStringObject(keysds,sdslen(keysds));
...@@ -760,7 +791,12 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { ...@@ -760,7 +791,12 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
} }
/* Filter element if it is an expired key. */ /* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1; if (!filter &&
o == NULL &&
expireIfNeededByName(c->db,kobj))
{
filter = 1;
}
/* Remove the element and its associted value if needed. */ /* Remove the element and its associted value if needed. */
if (filter) { if (filter) {
...@@ -819,7 +855,7 @@ void typeCommand(client *c) { ...@@ -819,7 +855,7 @@ void typeCommand(client *c) {
robj *o; robj *o;
char *type; char *type;
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH); o = lookupKeyReadWithFlags(c->db,c->argv[1],NULL,LOOKUP_NOTOUCH);
if (o == NULL) { if (o == NULL) {
type = "none"; type = "none";
} else { } else {
...@@ -870,6 +906,7 @@ void shutdownCommand(client *c) { ...@@ -870,6 +906,7 @@ void shutdownCommand(client *c) {
void renameGenericCommand(client *c, int nx) { void renameGenericCommand(client *c, int nx) {
robj *o; robj *o;
rkey *key;
long long expire; long long expire;
int samekey = 0; int samekey = 0;
...@@ -877,7 +914,7 @@ void renameGenericCommand(client *c, int nx) { ...@@ -877,7 +914,7 @@ void renameGenericCommand(client *c, int nx) {
* if the key exists, however we still return an error on unexisting key. */ * if the key exists, however we still return an error on unexisting key. */
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1; if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) if ((o = lookupKeyWriteOrReply(c,c->argv[1],&key,shared.nokeyerr)) == NULL)
return; return;
if (samekey) { if (samekey) {
...@@ -886,8 +923,8 @@ void renameGenericCommand(client *c, int nx) { ...@@ -886,8 +923,8 @@ void renameGenericCommand(client *c, int nx) {
} }
incrRefCount(o); incrRefCount(o);
expire = getExpire(c->db,c->argv[1]); expire = getExpire(key);
if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { if (lookupKeyWrite(c->db,c->argv[2],NULL) != NULL) {
if (nx) { if (nx) {
decrRefCount(o); decrRefCount(o);
addReply(c,shared.czero); addReply(c,shared.czero);
...@@ -897,8 +934,8 @@ void renameGenericCommand(client *c, int nx) { ...@@ -897,8 +934,8 @@ void renameGenericCommand(client *c, int nx) {
* with the same name. */ * with the same name. */
dbDelete(c->db,c->argv[2]); dbDelete(c->db,c->argv[2]);
} }
dbAdd(c->db,c->argv[2],o); key = dbAdd(c->db,c->argv[2],o);
if (expire != -1) setExpire(c,c->db,c->argv[2],expire); if (expire != -1) setExpire(c,c->db,key,expire);
dbDelete(c->db,c->argv[1]); dbDelete(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[2]); signalModifiedKey(c->db,c->argv[2]);
...@@ -951,20 +988,21 @@ void moveCommand(client *c) { ...@@ -951,20 +988,21 @@ void moveCommand(client *c) {
} }
/* Check if the element exists and get a reference */ /* Check if the element exists and get a reference */
o = lookupKeyWrite(c->db,c->argv[1]); rkey *key;
o = lookupKeyWrite(c->db,c->argv[1],&key);
if (!o) { if (!o) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
expire = getExpire(c->db,c->argv[1]); expire = getExpire(key);
/* Return zero if the key already exists in the target DB */ /* Return zero if the key already exists in the target DB */
if (lookupKeyWrite(dst,c->argv[1]) != NULL) { if (lookupKeyWrite(dst,c->argv[1],NULL) != NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
dbAdd(dst,c->argv[1],o); key = dbAdd(dst,c->argv[1],o);
if (expire != -1) setExpire(c,dst,c->argv[1],expire); if (expire != -1) setExpire(c,dst,key,expire);
incrRefCount(o); incrRefCount(o);
/* OK! key moved, free the entry in the source DB */ /* OK! key moved, free the entry in the source DB */
...@@ -982,7 +1020,7 @@ void scanDatabaseForReadyLists(redisDb *db) { ...@@ -982,7 +1020,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
dictIterator *di = dictGetSafeIterator(db->blocking_keys); dictIterator *di = dictGetSafeIterator(db->blocking_keys);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
robj *key = dictGetKey(de); robj *key = dictGetKey(de);
robj *value = lookupKey(db,key,LOOKUP_NOTOUCH); robj *value = lookupKey(db,key,NULL,LOOKUP_NOTOUCH);
if (value && (value->type == OBJ_LIST || if (value && (value->type == OBJ_LIST ||
value->type == OBJ_STREAM || value->type == OBJ_STREAM ||
value->type == OBJ_ZSET)) value->type == OBJ_ZSET))
...@@ -1060,134 +1098,6 @@ void swapdbCommand(client *c) { ...@@ -1060,134 +1098,6 @@ void swapdbCommand(client *c) {
} }
} }
/*-----------------------------------------------------------------------------
* Expires API
*----------------------------------------------------------------------------*/
int removeExpire(redisDb *db, robj *key) {
/* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
return dictDelete(db->expires,key->ptr) == DICT_OK;
}
/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
* after which the key will no longer be considered valid. */
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de;
/* Reuse the sds from the main dict in the expire dict */
kde = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
de = dictAddOrFind(db->expires,dictGetKey(kde));
dictSetSignedIntegerVal(de,when);
int writable_slave = server.masterhost && server.repl_slave_ro == 0;
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
rememberSlaveKeyWithExpire(db,key);
}
/* Return the expire time of the specified key, or -1 if no expire
* is associated with this key (i.e. the key is non volatile) */
long long getExpire(redisDb *db, robj *key) {
dictEntry *de;
/* No expire? return ASAP */
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
/* The entry was found in the expire dict, this means it should also
* be present in the main dict (safety check). */
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
return dictGetSignedIntegerVal(de);
}
/* Propagate expires into slaves and the AOF file.
* When a key expires in the master, a DEL operation for this key is sent
* to all the slaves and the AOF file if enabled.
*
* This way the key expiry is centralized in one place, and since both
* AOF and the master->slave link guarantee operation ordering, everything
* will be consistent even if we allow write operations against expiring
* keys. */
void propagateExpire(redisDb *db, robj *key, int lazy) {
robj *argv[2];
argv[0] = lazy ? shared.unlink : shared.del;
argv[1] = key;
incrRefCount(argv[0]);
incrRefCount(argv[1]);
if (server.aof_state != AOF_OFF)
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
/* Check if the key is expired. */
int keyIsExpired(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
return now > when;
}
/* This function is called when we are going to perform some operation
* in a given key, but such key may be already logically expired even if
* it still exists in the database. The main way this function is called
* is via lookupKey*() family of functions.
*
* The behavior of the function depends on the replication role of the
* instance, because slave instances do not expire keys, they wait
* for DELs from the master for consistency matters. However even
* slaves will try to have a coherent return value for the function,
* so that read commands executed in the slave side will be able to
* behave like if the key is expired even if still present (because the
* master has yet to propagate the DEL).
*
* In masters as a side effect of finding a key which is expired, such
* key will be evicted from the database. Also this may trigger the
* propagation of a DEL/UNLINK command in AOF / replication stream.
*
* The return value of the function is 0 if the key is still valid,
* otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *key) {
if (!keyIsExpired(db,key)) return 0;
/* If we are running in the context of a slave, instead of
* evicting the expired key from the database, we return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) return 1;
/* Delete the key */
server.stat_expiredkeys++;
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* API to get key arguments from commands * API to get key arguments from commands
* ---------------------------------------------------------------------------*/ * ---------------------------------------------------------------------------*/
......
...@@ -118,10 +118,10 @@ void mixStringObjectDigest(unsigned char *digest, robj *o) { ...@@ -118,10 +118,10 @@ void mixStringObjectDigest(unsigned char *digest, robj *o) {
* Note that this function does not reset the initial 'digest' passed, it * Note that this function does not reset the initial 'digest' passed, it
* will continue mixing this object digest to anything that was already * will continue mixing this object digest to anything that was already
* present. */ * present. */
void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) { void xorObjectDigest(rkey *key, unsigned char *digest, robj *o) {
uint32_t aux = htonl(o->type); uint32_t aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux)); mixDigest(digest,&aux,sizeof(aux));
long long expiretime = getExpire(db,keyobj); long long expiretime = getExpire(key);
char buf[128]; char buf[128];
/* Save the key and associated value */ /* Save the key and associated value */
...@@ -277,21 +277,19 @@ void computeDatasetDigest(unsigned char *final) { ...@@ -277,21 +277,19 @@ void computeDatasetDigest(unsigned char *final) {
/* Iterate this DB writing every entry */ /* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
sds key; rkey *key;
robj *keyobj, *o; robj *o;
memset(digest,0,20); /* This key-val digest */ memset(digest,0,20); /* This key-val digest */
key = dictGetKey(de); key = dictGetKey(de);
keyobj = createStringObject(key,sdslen(key));
mixDigest(digest,key,sdslen(key)); mixDigest(digest,key->name,key->len);
o = dictGetVal(de); o = dictGetVal(de);
xorObjectDigest(db,keyobj,digest,o); xorObjectDigest(key,digest,o);
/* We can finally xor the key-val digest to the final digest */ /* We can finally xor the key-val digest to the final digest */
xorDigest(final,digest,20); xorDigest(final,digest,20);
decrRefCount(keyobj);
} }
dictReleaseIterator(di); dictReleaseIterator(di);
} }
...@@ -385,6 +383,7 @@ NULL ...@@ -385,6 +383,7 @@ NULL
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) {
dictEntry *de; dictEntry *de;
rkey *key;
robj *val; robj *val;
char *strenc; char *strenc;
...@@ -392,6 +391,7 @@ NULL ...@@ -392,6 +391,7 @@ NULL
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
return; return;
} }
key = dictGetKey(de);
val = dictGetVal(de); val = dictGetVal(de);
strenc = strEncoding(val->encoding); strenc = strEncoding(val->encoding);
...@@ -434,7 +434,7 @@ NULL ...@@ -434,7 +434,7 @@ NULL
"lru:%d lru_seconds_idle:%llu%s", "lru:%d lru_seconds_idle:%llu%s",
(void*)val, val->refcount, (void*)val, val->refcount,
strenc, rdbSavedObjectLen(val), strenc, rdbSavedObjectLen(val),
val->lru, estimateObjectIdleTime(val)/1000, extra); key->lru, estimateObjectIdleTime(key)/1000, extra);
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
dictEntry *de; dictEntry *de;
robj *val; robj *val;
...@@ -463,7 +463,7 @@ NULL ...@@ -463,7 +463,7 @@ NULL
} else if (!strcasecmp(c->argv[1]->ptr,"ziplist") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"ziplist") && c->argc == 3) {
robj *o; robj *o;
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr)) if ((o = objectCommandLookupOrReply(c,c->argv[2],NULL,shared.nokeyerr))
== NULL) return; == NULL) return;
if (o->encoding != OBJ_ENCODING_ZIPLIST) { if (o->encoding != OBJ_ENCODING_ZIPLIST) {
...@@ -489,7 +489,7 @@ NULL ...@@ -489,7 +489,7 @@ NULL
if (c->argc == 5) if (c->argc == 5)
if (getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK) if (getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK)
return; return;
if (lookupKeyWrite(c->db,key) != NULL) { if (lookupKeyWrite(c->db,key,NULL) != NULL) {
decrRefCount(key); decrRefCount(key);
continue; continue;
} }
...@@ -521,8 +521,10 @@ NULL ...@@ -521,8 +521,10 @@ NULL
for (int j = 2; j < c->argc; j++) { for (int j = 2; j < c->argc; j++) {
unsigned char digest[20]; unsigned char digest[20];
memset(digest,0,20); /* Start with a clean result */ memset(digest,0,20); /* Start with a clean result */
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH); rkey *key;
if (o) xorObjectDigest(c->db,c->argv[j],digest,o); robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],&key,
LOOKUP_NOTOUCH);
if (o) xorObjectDigest(key,digest,o);
sds d = sdsempty(); sds d = sdsempty();
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]); for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
...@@ -634,16 +636,12 @@ NULL ...@@ -634,16 +636,12 @@ NULL
dictGetStats(buf,sizeof(buf),server.db[dbid].dict); dictGetStats(buf,sizeof(buf),server.db[dbid].dict);
stats = sdscat(stats,buf); stats = sdscat(stats,buf);
stats = sdscatprintf(stats,"[Expires HT]\n");
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
stats = sdscat(stats,buf);
addReplyBulkSds(c,stats); addReplyBulkSds(c,stats);
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
robj *o; robj *o;
dict *ht = NULL; dict *ht = NULL;
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr)) if ((o = objectCommandLookupOrReply(c,c->argv[2],NULL,shared.nokeyerr))
== NULL) return; == NULL) return;
/* Get the hash table reference from the object, if possible. */ /* Get the hash table reference from the object, if possible. */
......
...@@ -775,7 +775,7 @@ long defragKey(redisDb *db, dictEntry *de) { ...@@ -775,7 +775,7 @@ long defragKey(redisDb *db, dictEntry *de) {
/* Dirty code: /* Dirty code:
* I can't search in db->expires for that key after i already released * I can't search in db->expires for that key after i already released
* the pointer it holds it won't be able to do the string compare */ * the pointer it holds it won't be able to do the string compare */
uint64_t hash = dictGetHash(db->dict, de->key); uint64_t hash = dictGetEntryHash(db->dict, de);
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged); replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
} }
......
...@@ -206,7 +206,7 @@ int dictRehash(dict *d, int n) { ...@@ -206,7 +206,7 @@ int dictRehash(dict *d, int n) {
nextde = de->next; nextde = de->next;
/* Get the index in the new hash table */ /* Get the index in the new hash table */
h = dictHashKey(d, de->key) & d->ht[1].sizemask; h = dictHashStoredKey(d, de->key) & d->ht[1].sizemask;
de->next = d->ht[1].table[h]; de->next = d->ht[1].table[h];
d->ht[1].table[h] = de; d->ht[1].table[h] = de;
d->ht[0].used--; d->ht[0].used--;
...@@ -299,7 +299,7 @@ dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) ...@@ -299,7 +299,7 @@ dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
/* Get the index of the new element, or -1 if /* Get the index of the new element, or -1 if
* the element already exists. */ * the element already exists. */
if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1) if ((index = _dictKeyIndex(d,key,dictHashStoredKey(d,key),existing)) == -1)
return NULL; return NULL;
/* Allocate the memory and store the new entry. /* Allocate the memory and store the new entry.
...@@ -369,14 +369,14 @@ static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) { ...@@ -369,14 +369,14 @@ static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL; if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;
if (dictIsRehashing(d)) _dictRehashStep(d); if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key); h = dictHashLookupKey(d, key);
for (table = 0; table <= 1; table++) { for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask; idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx]; he = d->ht[table].table[idx];
prevHe = NULL; prevHe = NULL;
while(he) { while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key)) { if (key==he->key || dictCompareLookupKeys(d, key, he->key)) {
/* Unlink the element from the list */ /* Unlink the element from the list */
if (prevHe) if (prevHe)
prevHe->next = he->next; prevHe->next = he->next;
...@@ -480,12 +480,12 @@ dictEntry *dictFind(dict *d, const void *key) ...@@ -480,12 +480,12 @@ dictEntry *dictFind(dict *d, const void *key)
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */ if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
if (dictIsRehashing(d)) _dictRehashStep(d); if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key); h = dictHashLookupKey(d, key);
for (table = 0; table <= 1; table++) { for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask; idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx]; he = d->ht[table].table[idx];
while(he) { while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key)) if (key==he->key || dictCompareLookupKeys(d, key, he->key))
return he; return he;
he = he->next; he = he->next;
} }
...@@ -998,7 +998,7 @@ static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **e ...@@ -998,7 +998,7 @@ static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **e
/* Search if this slot does not already contain the given key */ /* Search if this slot does not already contain the given key */
he = d->ht[table].table[idx]; he = d->ht[table].table[idx];
while(he) { while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key)) { if (key==he->key || dictCompareStoredKeys(d, key, he->key)) {
if (existing) *existing = he; if (existing) *existing = he;
return -1; return -1;
} }
...@@ -1024,15 +1024,28 @@ void dictDisableResize(void) { ...@@ -1024,15 +1024,28 @@ void dictDisableResize(void) {
dict_can_resize = 0; dict_can_resize = 0;
} }
/* Compute the hash of the specified key (using the lookup hash function)
* and returns it to the caller. This is useful in order to later call
* dictFindEntryRefByPtrAndHash(). */
uint64_t dictGetHash(dict *d, const void *key) { uint64_t dictGetHash(dict *d, const void *key) {
return dictHashKey(d, key); return dictHashLookupKey(d, key);
}
/* Compute the hash of the specified dict entry (using the stored keys hash
* function) and returns it to the caller. This is useful in order to later
* call dictFindEntryRefByPtrAndHash(). */
uint64_t dictGetEntryHash(dict *d, const dictEntry *de) {
return dictHashStoredKey(d, de->key);
} }
/* Finds the dictEntry reference by using pointer and pre-calculated hash. /* Finds the dictEntry reference by using pointer and pre-calculated hash.
* oldkey is a dead pointer and should not be accessed. * oldkey is a dead pointer and should not be accessed.
* the hash value should be provided using dictGetHash. * the hash value should be provided using dictGetHash() or dictGetEntryHash()
* no string / key comparison is performed. * depending on the object type (if the one we lookup the hash table with, or
* return value is the reference to the dictEntry if found, or NULL if not found. */ * the one stored in the dict entry).
* No string / key comparison is performed.
* Return value is the reference to the dictEntry if found, or NULL if not
* found. */
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash) { dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash) {
dictEntry *he, **heref; dictEntry *he, **heref;
unsigned long idx, table; unsigned long idx, table;
...@@ -1159,10 +1172,12 @@ void freeCallback(void *privdata, void *val) { ...@@ -1159,10 +1172,12 @@ void freeCallback(void *privdata, void *val) {
} }
dictType BenchmarkDictType = { dictType BenchmarkDictType = {
hashCallback,
hashCallback, hashCallback,
NULL, NULL,
NULL, NULL,
compareCallback, compareCallback,
compareCallback,
freeCallback, freeCallback,
NULL NULL
}; };
......
...@@ -44,6 +44,8 @@ ...@@ -44,6 +44,8 @@
/* Unused arguments generate annoying warnings... */ /* Unused arguments generate annoying warnings... */
#define DICT_NOTUSED(V) ((void) V) #define DICT_NOTUSED(V) ((void) V)
/* An entry (a key basically) as stored inside the hash table. Note that
* there is a 'next' pointer, since we use chaining to resolve conflicts. */
typedef struct dictEntry { typedef struct dictEntry {
void *key; void *key;
union { union {
...@@ -55,11 +57,33 @@ typedef struct dictEntry { ...@@ -55,11 +57,33 @@ typedef struct dictEntry {
struct dictEntry *next; struct dictEntry *next;
} dictEntry; } dictEntry;
/* The hash table type.
*
* There are two hash functions both for key hashing and comparison, one set is
* used for looking up the key and the other set to store it.
*
* Most of the times each set will be the same function pointer, but sometimes
* we want the ability to store a key in a given way inside the hash function,
* and lookup it in some other way without resorting to any kind of conversion.
* For instance the key may be stored as a structure also representing other
* things, but the lookup happens via just a pointer to a null terminated
* string. The dual hash design allows for such usage. In that case we'll have
* a lookupHashFunction that will expect a null terminated C string, and a
* storeHashFunction that will instead expect the structure.
* Similarly the two comparison functions will work differently. The
* lookupKeyCompare will treat the first argument as a pointer to a C string
* and the other as a structure (this way we can directly lookup the structure
* key using the C string). While the storedKeyCompare() will check if two
* pointers to the key in structure form are the same.
*
* Every actual hash table have a pointer to its type. */
typedef struct dictType { typedef struct dictType {
uint64_t (*hashFunction)(const void *key); uint64_t (*lookupHashFunction)(const void *key);
uint64_t (*storedHashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key); void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj); void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2); int (*lookupKeyCompare)(void *privdata, const void *key1, const void *key2);
int (*storedKeyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key); void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj); void (*valDestructor)(void *privdata, void *obj);
} dictType; } dictType;
...@@ -132,12 +156,18 @@ typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref); ...@@ -132,12 +156,18 @@ typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
(entry)->key = (_key_); \ (entry)->key = (_key_); \
} while(0) } while(0)
#define dictCompareKeys(d, key1, key2) \ #define dictCompareLookupKeys(d, key1, key2) \
(((d)->type->keyCompare) ? \ (((d)->type->lookupKeyCompare) ? \
(d)->type->keyCompare((d)->privdata, key1, key2) : \ (d)->type->lookupKeyCompare((d)->privdata, key1, key2) : \
(key1) == (key2))
#define dictCompareStoredKeys(d, key1, key2) \
(((d)->type->storedKeyCompare) ? \
(d)->type->storedKeyCompare((d)->privdata, key1, key2) : \
(key1) == (key2)) (key1) == (key2))
#define dictHashKey(d, key) (d)->type->hashFunction(key) #define dictHashLookupKey(d, key) (d)->type->lookupHashFunction(key)
#define dictHashStoredKey(d, key) (d)->type->storedHashFunction(key)
#define dictGetKey(he) ((he)->key) #define dictGetKey(he) ((he)->key)
#define dictGetVal(he) ((he)->v.val) #define dictGetVal(he) ((he)->v.val)
#define dictGetSignedIntegerVal(he) ((he)->v.s64) #define dictGetSignedIntegerVal(he) ((he)->v.s64)
...@@ -180,6 +210,7 @@ void dictSetHashFunctionSeed(uint8_t *seed); ...@@ -180,6 +210,7 @@ void dictSetHashFunctionSeed(uint8_t *seed);
uint8_t *dictGetHashFunctionSeed(void); uint8_t *dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata); unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
uint64_t dictGetHash(dict *d, const void *key); uint64_t dictGetHash(dict *d, const void *key);
uint64_t dictGetEntryHash(dict *d, const dictEntry *de);
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash); dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);
/* Hash table types */ /* Hash table types */
......
...@@ -87,12 +87,12 @@ unsigned int LRU_CLOCK(void) { ...@@ -87,12 +87,12 @@ unsigned int LRU_CLOCK(void) {
/* Given an object returns the min number of milliseconds the object was never /* Given an object returns the min number of milliseconds the object was never
* requested, using an approximated LRU algorithm. */ * requested, using an approximated LRU algorithm. */
unsigned long long estimateObjectIdleTime(robj *o) { unsigned long long estimateObjectIdleTime(rkey *key) {
unsigned long long lruclock = LRU_CLOCK(); unsigned long long lruclock = LRU_CLOCK();
if (lruclock >= o->lru) { if (lruclock >= key->lru) {
return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION; return (lruclock - key->lru) * LRU_CLOCK_RESOLUTION;
} else { } else {
return (lruclock + (LRU_CLOCK_MAX - o->lru)) * return (lruclock + (LRU_CLOCK_MAX - key->lru)) *
LRU_CLOCK_RESOLUTION; LRU_CLOCK_RESOLUTION;
} }
} }
...@@ -166,7 +166,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic ...@@ -166,7 +166,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples); count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
unsigned long long idle; unsigned long long idle;
sds key; rkey *key;
robj *o; robj *o;
dictEntry *de; dictEntry *de;
...@@ -185,7 +185,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic ...@@ -185,7 +185,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
* 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 an higher score means better candidate. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = estimateObjectIdleTime(o); idle = estimateObjectIdleTime(key);
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { } else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
/* When we use an LRU policy, we sort the keys by idle time /* When we use an LRU policy, we sort the keys by idle time
* so that we expire keys starting from greater idle time. * so that we expire keys starting from greater idle time.
...@@ -194,10 +194,10 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic ...@@ -194,10 +194,10 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
* first. So inside the pool we put objects using the inverted * first. So inside the pool we put objects using the inverted
* frequency subtracting the actual frequency to the maximum * frequency subtracting the actual frequency to the maximum
* frequency of 255. */ * frequency of 255. */
idle = 255-LFUDecrAndReturn(o); idle = 255-LFUDecrAndReturn(key);
} else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) { } else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
/* In this case the sooner the expire the better. */ /* In this case the sooner the expire the better. */
idle = ULLONG_MAX - (long)dictGetVal(de); idle = key->expire;
} else { } else {
serverPanic("Unknown eviction policy in evictionPoolPopulate()"); serverPanic("Unknown eviction policy in evictionPoolPopulate()");
} }
...@@ -243,9 +243,9 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic ...@@ -243,9 +243,9 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
* because allocating and deallocating this object is costly * because allocating and deallocating this object is costly
* (according to the profiler, not my fantasy. Remember: * (according to the profiler, not my fantasy. Remember:
* premature optimizbla bla bla bla. */ * premature optimizbla bla bla bla. */
int klen = sdslen(key); int klen = key->len;
if (klen > EVPOOL_CACHED_SDS_SIZE) { if (klen > EVPOOL_CACHED_SDS_SIZE) {
pool[k].key = sdsdup(key); pool[k].key = sdsnewlen(key->name,key->len);
} else { } else {
memcpy(pool[k].cached,key,klen+1); memcpy(pool[k].cached,key,klen+1);
sdssetlen(pool[k].cached,klen); sdssetlen(pool[k].cached,klen);
...@@ -332,9 +332,9 @@ uint8_t LFULogIncr(uint8_t counter) { ...@@ -332,9 +332,9 @@ uint8_t LFULogIncr(uint8_t counter) {
* This function is used in order to scan the dataset for the best object * 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 * to fit: as we check for the candidate, we incrementally decrement the
* counter of the scanned objects if needed. */ * counter of the scanned objects if needed. */
unsigned long LFUDecrAndReturn(robj *o) { unsigned long LFUDecrAndReturn(rkey *k) {
unsigned long ldt = o->lru >> 8; unsigned long ldt = k->lru >> 8;
unsigned long counter = o->lru & 255; unsigned long counter = k->lru & 255;
unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0; unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;
if (num_periods) if (num_periods)
counter = (num_periods > counter) ? 0 : counter - num_periods; counter = (num_periods > counter) ? 0 : counter - num_periods;
...@@ -488,8 +488,13 @@ int freeMemoryIfNeeded(void) { ...@@ -488,8 +488,13 @@ int freeMemoryIfNeeded(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;
#if 0
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ? dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
db->dict : db->expires; db->dict : db->expires;
#else
dict = db->dict;
#warning "Implement the volatile policy correctly"
#endif
if ((keys = dictSize(dict)) != 0) { if ((keys = dictSize(dict)) != 0) {
evictionPoolPopulate(i, dict, db->dict, pool); evictionPoolPopulate(i, dict, db->dict, pool);
total_keys += keys; total_keys += keys;
...@@ -506,8 +511,11 @@ int freeMemoryIfNeeded(void) { ...@@ -506,8 +511,11 @@ int freeMemoryIfNeeded(void) {
de = dictFind(server.db[pool[k].dbid].dict, de = dictFind(server.db[pool[k].dbid].dict,
pool[k].key); pool[k].key);
} else { } else {
#warning "Implement the volatile policy correctly"
#if 0
de = dictFind(server.db[pool[k].dbid].expires, de = dictFind(server.db[pool[k].dbid].expires,
pool[k].key); pool[k].key);
#endif
} }
/* Remove the entry from the pool. */ /* Remove the entry from the pool. */
...@@ -538,8 +546,13 @@ int freeMemoryIfNeeded(void) { ...@@ -538,8 +546,13 @@ int freeMemoryIfNeeded(void) {
for (i = 0; i < server.dbnum; i++) { for (i = 0; i < server.dbnum; i++) {
j = (++next_db) % server.dbnum; j = (++next_db) % server.dbnum;
db = server.db+j; db = server.db+j;
#if 0
dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ? dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
db->dict : db->expires; db->dict : db->expires;
#else
#warning "Implement the volatile policy correctly"
dict = db->dict;
#endif
if (dictSize(dict) != 0) { if (dictSize(dict) != 0) {
de = dictGetRandomKey(dict); de = dictGetRandomKey(dict);
bestkey = dictGetKey(de); bestkey = dictGetKey(de);
......
...@@ -38,9 +38,103 @@ ...@@ -38,9 +38,103 @@
* When keys are accessed they are expired on-access. However we need a * When keys are accessed they are expired on-access. However we need a
* mechanism in order to ensure keys are eventually removed when expired even * mechanism in order to ensure keys are eventually removed when expired even
* if no access is performed on them. * if no access is performed on them.
*
* In order to accomplish this every key with an expire is represented in
* two data structures:
*
* 1. The main dictionary of keys, server.db[x]->dict, is an hash table that
* represents the keyspace of a given Redis database. The keys stored
* in the hash table are redisKey structures (typedef 'rkey'). When
* a key has an expire set, the key->flags have the KEY_FLAG_EXPIRE set,
* and the key->expire is populated with the milliseconds unix time at
* which the key will no longer be valid.
*
* 2. Redis also takes a radix tree that is composed only of keys that have
* an expire set, lexicographically sorted by the expire time. Basically
* each key in the radix tree is composed as follows:
*
* [8 bytes expire unix time][8 bytes key object pointer]
*
* Such tree is stored in server.db[x]->expire.
*
* The first field, the unix time, is the same stored in the key->expire of
* the corresponding key in the hash table, however it is stored in big endian
* so that sorting the time lexicographically in the tree, will also make the
* tree sorted by numerical expire time (from the smallest unix time to the
* greatest one).
*
* Then we store the key pointer, this time in native endianess, because how
* it is sorted does not matter, being after the unix time. If Redis is running
* as a 32 bit system, the last 4 bytes of the pointer are just zeroes, so
* we can assume a 16 bytes key in every architecture. Note that from the
* pointer we can retrieve the key name, lookup it in the main dictionary, and
* delete the key.
*
* On the other hand, when we modify the expire time of some key, we need to
* update the tree accordingly. At every expire cycle, what we need to do is
* conceptually very simple: we run the tree and expire keys as long as we
* find keys that are already logically expired (expire time > current time).
*
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* Helper function for the activeExpireCycle() function. #define EXPIRE_KEY_LEN 16 /* Key length in the radix tree of expires. */
/* Populate the buffer 'buf', that should be at least EXPIRE_KEY_LEN bytes,
* with the key to store such key in the expires radix tree. See the comment
* above to see the format. */
void encodeExpireKey(unsigned char *buf, rkey *key) {
uint64_t expire = htonu64(key->expire);
uint64_t ptr = (uint64_t) key; /* The pointer may be 32 bit, cast to 64. */
memcpy(buf,&expire,sizeof(expire));
memcpy(buf+8,&ptr,sizeof(ptr));
}
/* This is the reverse of encodeExpireKey(): given the key will return a
* pointer to an rkey and the expire value. */
void decodeExpireKey(unsigned char *buf, uint64_t *expireptr, rkey **keyptrptr) {
uint64_t expire;
uint64_t keyptr;
memcpy(&expire,buf,sizeof(expire));
expire = ntohu64(expire);
memcpy(&keyptr,buf+8,sizeof(keyptr));
*expireptr = expire;
*keyptrptr = (rkey*)(unsigned long)keyptr;
}
/* Populate the expires radix tree with the specified key. */
void addExpireToTree(redisDb *db, rkey *key) {
unsigned char expirekey[EXPIRE_KEY_LEN];
encodeExpireKey(expirekey,key);
int retval = raxTryInsert(db->expires,expirekey,EXPIRE_KEY_LEN,NULL,NULL);
serverAssert(retval != 0);
}
/* Remove the specified key from the expires radix tree. */
void removeExpireFromTree(redisDb *db, rkey *key) {
unsigned char expirekey[EXPIRE_KEY_LEN];
encodeExpireKey(expirekey,key);
int retval = raxRemove(db->expires,expirekey,EXPIRE_KEY_LEN,NULL);
serverAssert(retval != 0);
}
/* Delete a key that is found expired by the expiration cycle. We need to
* propagate the key too, send the notification event, and take a few
* stats. */
void deleteExpiredKey(redisDb *db, rkey *key) {
robj *keyname = createStringObject(key->name,key->len);
propagateExpire(db,keyname,server.lazyfree_lazy_expire);
if (server.lazyfree_lazy_expire)
dbAsyncDelete(db,keyname);
else
dbSyncDelete(db,keyname);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyname,db->id);
decrRefCount(keyname);
server.stat_expiredkeys++;
}
/* Helper function for the expireSlaveKeys() function.
* This function will try to expire the key that is stored in the hash table * This function will try to expire the key that is stored in the hash table
* entry 'de' of the 'expires' hash table of a Redis database. * entry 'de' of the 'expires' hash table of a Redis database.
* *
...@@ -51,21 +145,10 @@ ...@@ -51,21 +145,10 @@
* *
* The parameter 'now' is the current time in milliseconds as is passed * The parameter 'now' is the current time in milliseconds as is passed
* to the function to avoid too many gettimeofday() syscalls. */ * to the function to avoid too many gettimeofday() syscalls. */
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) { int activeExpireCycleTryExpire(redisDb *db, rkey *key, long long now) {
long long t = dictGetSignedIntegerVal(de); long long t = key->expire;
if (now > t) { if (now > t) {
sds key = dictGetKey(de); deleteExpiredKey(db,key);
robj *keyobj = createStringObject(key,sdslen(key));
propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
if (server.lazyfree_lazy_expire)
dbAsyncDelete(db,keyobj);
else
dbSyncDelete(db,keyobj);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyobj,db->id);
decrRefCount(keyobj);
server.stat_expiredkeys++;
return 1; return 1;
} else { } else {
return 0; return 0;
...@@ -101,7 +184,8 @@ void activeExpireCycle(int type) { ...@@ -101,7 +184,8 @@ void activeExpireCycle(int type) {
static int timelimit_exit = 0; /* Time limit hit in previous call? */ static int timelimit_exit = 0; /* Time limit hit in previous call? */
static long long last_fast_cycle = 0; /* When last fast cycle ran. */ static long long last_fast_cycle = 0; /* When last fast cycle ran. */
int j, iteration = 0; int j;
unsigned long iteration = 0;
int dbs_per_call = CRON_DBS_PER_CALL; int dbs_per_call = CRON_DBS_PER_CALL;
long long start = ustime(), timelimit, elapsed; long long start = ustime(), timelimit, elapsed;
...@@ -129,25 +213,20 @@ void activeExpireCycle(int type) { ...@@ -129,25 +213,20 @@ void activeExpireCycle(int type) {
if (dbs_per_call > server.dbnum || timelimit_exit) if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum; dbs_per_call = server.dbnum;
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time /* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of
* per iteration. Since this function gets called with a frequency of * CPU time per iteration. Since this function gets called with a
* server.hz times per second, the following is the max amount of * frequency of server.hz times per second, the following is the max
* microseconds we can spend in this function. */ * amount of microseconds we can spend in this function. */
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100; timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
timelimit_exit = 0; timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1; if (timelimit <= 0) timelimit = 1;
/* If it's a fast cycle, override the time limit with our fixed
* time limit (defaults to 1 millisecond). */
if (type == ACTIVE_EXPIRE_CYCLE_FAST) if (type == ACTIVE_EXPIRE_CYCLE_FAST)
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */ timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
/* Accumulate some global stats as we expire keys, to have some idea
* about the number of keys that are already logically expired, but still
* existing inside the database. */
long total_sampled = 0;
long total_expired = 0;
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) { for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
int expired;
redisDb *db = server.db+(current_db % server.dbnum); redisDb *db = server.db+(current_db % server.dbnum);
/* Increment the DB now so we are sure if we run out of time /* Increment the DB now so we are sure if we run out of time
...@@ -155,92 +234,59 @@ void activeExpireCycle(int type) { ...@@ -155,92 +234,59 @@ void activeExpireCycle(int type) {
* distribute the time evenly across DBs. */ * distribute the time evenly across DBs. */
current_db++; current_db++;
/* Continue to expire if at the end of the cycle more than 25% /* If there is nothing to expire try next DB ASAP, avoiding the
* of the keys were expired. */ * cost of seeking the radix tree iterator. */
do { if (raxSize(db->expires) == 0) continue;
unsigned long num, slots;
long long now, ttl_sum; /* The main collection cycle. Run the tree and expire keys that
int ttl_samples; * are found to be already logically expired. */
long long now = mstime();
raxIterator ri;
raxStart(&ri,db->expires);
raxSeek(&ri,"^",NULL,0);
/* Enter the loop expiring keys for this database. Inside this
* loop there are two stop conditions:
*
* 1. The time limit.
* 2. The loop will exit if in this DB there are no more keys
* that are logically expired.
*
* Moreover the loop naturally terminates when there are no longer
* elements in the radix tree. */
while(raxNext(&ri)) {
rkey *key;
uint64_t expire;
decodeExpireKey(ri.key,&expire,&key);
/* First stop condition: no keys to expire here. */
if (expire >= (uint64_t)now) break;
printf("DEL %.*s -> %llu\n", (int)key->len, key->name, expire);
deleteExpiredKey(db,key);
/* Second stop condition: the time limit. */
iteration++; iteration++;
if ((iteration & 0xff) == 0) {
/* If there is nothing to expire try next DB ASAP. */ now = ustime();
if ((num = dictSize(db->expires)) == 0) { elapsed = now-start;
db->avg_ttl = 0; now /= 1000; /* Convert back now to milliseconds. */
break;
}
slots = dictSlots(db->expires);
now = mstime();
/* When there are less than 1% filled slots getting random
* keys is expensive, so stop here waiting for better times...
* The dictionary will be resized asap. */
if (num && slots > DICT_HT_INITIAL_SIZE &&
(num*100/slots < 1)) break;
/* The main collection cycle. Sample random keys among keys
* with an expire set, checking for expired ones. */
expired = 0;
ttl_sum = 0;
ttl_samples = 0;
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
while (num--) {
dictEntry *de;
long long ttl;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
ttl = dictGetSignedIntegerVal(de)-now;
if (activeExpireCycleTryExpire(db,de,now)) expired++;
if (ttl > 0) {
/* We want the average TTL of keys yet not expired. */
ttl_sum += ttl;
ttl_samples++;
}
total_sampled++;
}
total_expired += expired;
/* Update the average TTL stats for this database. */
if (ttl_samples) {
long long avg_ttl = ttl_sum/ttl_samples;
/* Do a simple running average with a few samples.
* We just use the current estimate with a weight of 2%
* and the previous estimate with a weight of 98%. */
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
/* We can't block forever here even if there are many keys to
* expire. So after a given amount of milliseconds return to the
* caller waiting for the other active expire cycle. */
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
elapsed = ustime()-start;
if (elapsed > timelimit) { if (elapsed > timelimit) {
timelimit_exit = 1; timelimit_exit = 1;
printf("LIMIT (%llu) type:%d [elapsed=%llu]\n", timelimit, type, elapsed);
server.stat_expired_time_cap_reached_count++; server.stat_expired_time_cap_reached_count++;
break; break;
} }
} }
/* We don't repeat the cycle if there are less than 25% of keys /* Reseek the iterator: the node we were on is now
* found expired in the current DB. */ * deleted. */
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4); raxSeek(&ri,"^",NULL,0);
}
raxStop(&ri);
} }
elapsed = ustime()-start; elapsed = ustime()-start;
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000); latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
/* Update our estimate of keys existing but yet to be expired.
* Running average with this sample accounting for 5%. */
double current_perc;
if (total_sampled) {
current_perc = (double)total_expired/total_sampled;
} else
current_perc = 0;
server.stat_expired_stale_perc = (current_perc*0.05)+
(server.stat_expired_stale_perc*0.95);
} }
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
...@@ -269,8 +315,8 @@ void activeExpireCycle(int type) { ...@@ -269,8 +315,8 @@ void activeExpireCycle(int type) {
/* The dictionary where we remember key names and database ID of keys we may /* The dictionary where we remember key names and database ID of keys we may
* want to expire from the slave. Since this function is not often used we * want to expire from the slave. Since this function is not often used we
* don't even care to initialize the database at startup. We'll do it once * don't even care to initialize the database at startup. We'll do it once
* the feature is used the first time, that is, when rememberSlaveKeyWithExpire() * the feature is used the first time, that is, when the function
* is called. * rememberSlaveKeyWithExpire() is called.
* *
* The dictionary has an SDS string representing the key as the hash table * The dictionary has an SDS string representing the key as the hash table
* key, while the value is a 64 bit unsigned integer with the bits corresponding * key, while the value is a 64 bit unsigned integer with the bits corresponding
...@@ -300,11 +346,12 @@ void expireSlaveKeys(void) { ...@@ -300,11 +346,12 @@ void expireSlaveKeys(void) {
while(dbids && dbid < server.dbnum) { while(dbids && dbid < server.dbnum) {
if ((dbids & 1) != 0) { if ((dbids & 1) != 0) {
redisDb *db = server.db+dbid; redisDb *db = server.db+dbid;
dictEntry *expire = dictFind(db->expires,keyname); rkey *key = dictFetchValue(db->dict,keyname);
if (!(key->flags & KEY_FLAG_EXPIRE)) key = NULL;
int expired = 0; int expired = 0;
if (expire && if (key &&
activeExpireCycleTryExpire(server.db+dbid,expire,start)) activeExpireCycleTryExpire(server.db+dbid,key,start))
{ {
expired = 1; expired = 1;
} }
...@@ -313,7 +360,7 @@ void expireSlaveKeys(void) { ...@@ -313,7 +360,7 @@ void expireSlaveKeys(void) {
* corresponding bit in the new bitmap we set as value. * corresponding bit in the new bitmap we set as value.
* At the end of the loop if the bitmap is zero, it means we * At the end of the loop if the bitmap is zero, it means we
* no longer need to keep track of this key. */ * no longer need to keep track of this key. */
if (expire && !expired) { if (key && !expired) {
noexpire++; noexpire++;
new_dbids |= (uint64_t)1 << dbid; new_dbids |= (uint64_t)1 << dbid;
} }
...@@ -341,13 +388,15 @@ void expireSlaveKeys(void) { ...@@ -341,13 +388,15 @@ void expireSlaveKeys(void) {
/* Track keys that received an EXPIRE or similar command in the context /* Track keys that received an EXPIRE or similar command in the context
* of a writable slave. */ * of a writable slave. */
void rememberSlaveKeyWithExpire(redisDb *db, robj *key) { void rememberSlaveKeyWithExpire(redisDb *db, rkey *key) {
if (slaveKeysWithExpire == NULL) { if (slaveKeysWithExpire == NULL) {
static dictType dt = { static dictType dt = {
dictSdsHash, /* hash function */ dictSdsHash, /* lookup hash function */
dictSdsHash, /* stored hash function */
NULL, /* key dup */ NULL, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
dictSdsKeyCompare, /* key compare */ dictSdsKeyCompare, /* loopkup key compare */
dictSdsKeyCompare, /* stored key compare */
dictSdsDestructor, /* key destructor */ dictSdsDestructor, /* key destructor */
NULL /* val destructor */ NULL /* val destructor */
}; };
...@@ -355,13 +404,15 @@ void rememberSlaveKeyWithExpire(redisDb *db, robj *key) { ...@@ -355,13 +404,15 @@ void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
} }
if (db->id > 63) return; if (db->id > 63) return;
dictEntry *de = dictAddOrFind(slaveKeysWithExpire,key->ptr); sds skey = sdsnewlen(key->name,key->len);
/* If the entry was just created, set it to a copy of the SDS string dictEntry *de = dictAddOrFind(slaveKeysWithExpire,skey);
* representing the key: we don't want to need to take those keys /* If the entry was already there, free the SDS string we used to lookup.
* in sync with the main DB. The keys will be removed by expireSlaveKeys() * Note that we don't care to take those keys in sync with the
* as it scans to find keys to remove. */ * main DB. The keys will be removed by expireSlaveKeys() as it scans to
if (de->key == key->ptr) { * find keys to remove. */
de->key = sdsdup(key->ptr); if (de->key != skey) {
sdsfree(skey);
} else {
dictSetUnsignedIntegerVal(de,0); dictSetUnsignedIntegerVal(de,0);
} }
...@@ -391,6 +442,137 @@ void flushSlaveKeysWithExpireList(void) { ...@@ -391,6 +442,137 @@ void flushSlaveKeysWithExpireList(void) {
} }
} }
/*-----------------------------------------------------------------------------
* Expires API
*----------------------------------------------------------------------------*/
/* Remove the expire from the key making it persistent. */
int removeExpire(redisDb *db, rkey *key) {
if (!(key->flags & KEY_FLAG_EXPIRE)) return 0;
removeExpireFromTree(db,key);
key->flags &= ~KEY_FLAG_EXPIRE;
key->expire = 0; /* Not needed but better to leave the object clean. */
return 1;
}
/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
* after which the key will no longer be considered valid. */
void setExpire(client *c, redisDb *db, rkey *key, long long when) {
/* Reuse the sds from the main dict in the expire dict */
if (key->flags & KEY_FLAG_EXPIRE) removeExpireFromTree(db,key);
key->flags |= KEY_FLAG_EXPIRE;
key->expire = when;
addExpireToTree(db,key);
int writable_slave = server.masterhost && server.repl_slave_ro == 0;
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
rememberSlaveKeyWithExpire(db,key);
}
/* Return the expire time of the specified key, or -1 if no expire
* is associated with this key (i.e. the key is non volatile) */
long long getExpire(rkey *key) {
return (key->flags & KEY_FLAG_EXPIRE) ? key->expire : -1;
}
/* Propagate expires into slaves and the AOF file.
* When a key expires in the master, a DEL operation for this key is sent
* to all the slaves and the AOF file if enabled.
*
* This way the key expiry is centralized in one place, and since both
* AOF and the master->slave link guarantee operation ordering, everything
* will be consistent even if we allow write operations against expiring
* keys. */
void propagateExpire(redisDb *db, robj *key, int lazy) {
robj *argv[2];
argv[0] = lazy ? shared.unlink : shared.del;
argv[1] = key;
incrRefCount(argv[0]);
incrRefCount(argv[1]);
if (server.aof_state != AOF_OFF)
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
/* Check if the key is expired. */
int keyIsExpired(rkey *key) {
mstime_t when = getExpire(key);
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
return now > when;
}
/* This function is called when we are going to perform some operation
* in a given key, but such key may be already logically expired even if
* it still exists in the database. The main way this function is called
* is via lookupKey*() family of functions.
*
* The behavior of the function depends on the replication role of the
* instance, because slave instances do not expire keys, they wait
* for DELs from the master for consistency matters. However even
* slaves will try to have a coherent return value for the function,
* so that read commands executed in the slave side will be able to
* behave like if the key is expired even if still present (because the
* master has yet to propagate the DEL).
*
* In masters as a side effect of finding a key which is expired, such
* key will be evicted from the database. Also this may trigger the
* propagation of a DEL/UNLINK command in AOF / replication stream.
*
* The return value of the function is 0 if the key is still valid,
* otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *keyname, rkey *key) {
if (!keyIsExpired(key)) return 0;
/* If we are running in the context of a slave, instead of
* evicting the expired key from the database, we return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) return 1;
/* Delete the key */
server.stat_expiredkeys++;
propagateExpire(db,keyname,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyname,db->id);
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,keyname) :
dbSyncDelete(db,keyname);
}
/* Sometimes we have just the name of the key, because we have still to
* lookup it. In such cases this function is more handy compared to
* expireIfNeeded(): just a wrapper performing the lookup first. */
int expireIfNeededByName(redisDb *db, robj *keyname) {
rkey *key;
robj *val = lookupKey(db,keyname,&key,LOOKUP_NOTOUCH);
if (!val) return 0;
return expireIfNeeded(db,keyname,key);
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Expires Commands * Expires Commands
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
...@@ -403,7 +585,8 @@ void flushSlaveKeysWithExpireList(void) { ...@@ -403,7 +585,8 @@ void flushSlaveKeysWithExpireList(void) {
* unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
* the argv[2] parameter. The basetime is always specified in milliseconds. */ * the argv[2] parameter. The basetime is always specified in milliseconds. */
void expireGenericCommand(client *c, long long basetime, int unit) { void expireGenericCommand(client *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2]; robj *keyname = c->argv[1], *param = c->argv[2];
rkey *key;
long long when; /* unix time in milliseconds when the key will expire. */ long long when; /* unix time in milliseconds when the key will expire. */
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK) if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
...@@ -413,7 +596,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) { ...@@ -413,7 +596,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
when += basetime; when += basetime;
/* No key, return zero. */ /* No key, return zero. */
if (lookupKeyWrite(c->db,key) == NULL) { if (lookupKeyWrite(c->db,keyname,&key) == NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
...@@ -427,23 +610,24 @@ void expireGenericCommand(client *c, long long basetime, int unit) { ...@@ -427,23 +610,24 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
if (when <= mstime() && !server.loading && !server.masterhost) { if (when <= mstime() && !server.loading && !server.masterhost) {
robj *aux; robj *aux;
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) : int deleted = server.lazyfree_lazy_expire ?
dbSyncDelete(c->db,key); dbAsyncDelete(c->db,keyname) :
serverAssertWithInfo(c,key,deleted); dbSyncDelete(c->db,keyname);
serverAssertWithInfo(c,keyname,deleted);
server.dirty++; server.dirty++;
/* Replicate/AOF this as an explicit DEL or UNLINK. */ /* Replicate/AOF this as an explicit DEL or UNLINK. */
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del; aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
rewriteClientCommandVector(c,2,aux,key); rewriteClientCommandVector(c,2,aux,keyname);
signalModifiedKey(c->db,key); signalModifiedKey(c->db,keyname);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",keyname,c->db->id);
addReply(c, shared.cone); addReply(c, shared.cone);
return; return;
} else { } else {
setExpire(c,c->db,key,when); setExpire(c,c->db,key,when);
addReply(c,shared.cone); addReply(c,shared.cone);
signalModifiedKey(c->db,key); signalModifiedKey(c->db,keyname);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",keyname,c->db->id);
server.dirty++; server.dirty++;
return; return;
} }
...@@ -472,15 +656,16 @@ void pexpireatCommand(client *c) { ...@@ -472,15 +656,16 @@ void pexpireatCommand(client *c) {
/* Implements TTL and PTTL */ /* Implements TTL and PTTL */
void ttlGenericCommand(client *c, int output_ms) { void ttlGenericCommand(client *c, int output_ms) {
long long expire, ttl = -1; long long expire, ttl = -1;
rkey *key;
/* If the key does not exist at all, return -2 */ /* If the key does not exist at all, return -2 */
if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) { if (lookupKeyReadWithFlags(c->db,c->argv[1],&key,LOOKUP_NOTOUCH) == NULL) {
addReplyLongLong(c,-2); addReplyLongLong(c,-2);
return; return;
} }
/* The key exists. Return -1 if it has no expire, or the actual /* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */ * TTL value otherwise. */
expire = getExpire(c->db,c->argv[1]); expire = getExpire(key);
if (expire != -1) { if (expire != -1) {
ttl = expire-mstime(); ttl = expire-mstime();
if (ttl < 0) ttl = 0; if (ttl < 0) ttl = 0;
...@@ -504,8 +689,9 @@ void pttlCommand(client *c) { ...@@ -504,8 +689,9 @@ void pttlCommand(client *c) {
/* PERSIST key */ /* PERSIST key */
void persistCommand(client *c) { void persistCommand(client *c) {
if (lookupKeyWrite(c->db,c->argv[1])) { rkey *key;
if (removeExpire(c->db,c->argv[1])) { if (lookupKeyWrite(c->db,c->argv[1],&key)) {
if (removeExpire(c->db,key)) {
addReply(c,shared.cone); addReply(c,shared.cone);
server.dirty++; server.dirty++;
} else { } else {
...@@ -520,7 +706,7 @@ void persistCommand(client *c) { ...@@ -520,7 +706,7 @@ void persistCommand(client *c) {
void touchCommand(client *c) { void touchCommand(client *c) {
int touched = 0; int touched = 0;
for (int j = 1; j < c->argc; j++) for (int j = 1; j < c->argc; j++)
if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++; if (lookupKeyRead(c->db,c->argv[j],NULL) != NULL) touched++;
addReplyLongLong(c,touched); addReplyLongLong(c,touched);
} }
...@@ -466,8 +466,8 @@ void georadiusGeneric(client *c, int flags) { ...@@ -466,8 +466,8 @@ void georadiusGeneric(client *c, int flags) {
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = NULL; robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL || if ((zobj = lookupKeyReadOrReply(c, key, NULL, shared.null[c->resp]))
checkType(c, zobj, OBJ_ZSET)) { == NULL || checkType(c, zobj, OBJ_ZSET)) {
return; return;
} }
...@@ -701,7 +701,7 @@ void geohashCommand(client *c) { ...@@ -701,7 +701,7 @@ void geohashCommand(client *c) {
int j; int j;
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = lookupKeyRead(c->db, c->argv[1]); robj *zobj = lookupKeyRead(c->db, c->argv[1], NULL);
if (zobj && checkType(c, zobj, OBJ_ZSET)) return; if (zobj && checkType(c, zobj, OBJ_ZSET)) return;
/* Geohash elements one after the other, using a null bulk reply for /* Geohash elements one after the other, using a null bulk reply for
...@@ -754,7 +754,7 @@ void geoposCommand(client *c) { ...@@ -754,7 +754,7 @@ void geoposCommand(client *c) {
int j; int j;
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = lookupKeyRead(c->db, c->argv[1]); robj *zobj = lookupKeyRead(c->db, c->argv[1], NULL);
if (zobj && checkType(c, zobj, OBJ_ZSET)) return; if (zobj && checkType(c, zobj, OBJ_ZSET)) return;
/* Report elements one after the other, using a null bulk reply for /* Report elements one after the other, using a null bulk reply for
...@@ -797,7 +797,7 @@ void geodistCommand(client *c) { ...@@ -797,7 +797,7 @@ void geodistCommand(client *c) {
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = NULL; robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp])) if ((zobj = lookupKeyReadOrReply(c, c->argv[1], NULL, shared.null[c->resp]))
== NULL || checkType(c, zobj, OBJ_ZSET)) return; == NULL || checkType(c, zobj, OBJ_ZSET)) return;
/* Get the scores. We need both otherwise NULL is returned. */ /* Get the scores. We need both otherwise NULL is returned. */
......
...@@ -50,7 +50,7 @@ void addReplyGopherItem(client *c, const char *type, const char *descr, ...@@ -50,7 +50,7 @@ void addReplyGopherItem(client *c, const char *type, const char *descr,
* protocol. */ * protocol. */
void processGopherRequest(client *c) { void processGopherRequest(client *c) {
robj *keyname = c->argc == 0 ? createStringObject("/",1) : c->argv[0]; robj *keyname = c->argc == 0 ? createStringObject("/",1) : c->argv[0];
robj *o = lookupKeyRead(c->db,keyname); robj *o = lookupKeyRead(c->db,keyname,NULL);
/* If there is no such key, return with a Gopher error. */ /* If there is no such key, return with a Gopher error. */
if (o == NULL || o->type != OBJ_STRING) { if (o == NULL || o->type != OBJ_STRING) {
......
...@@ -1179,7 +1179,7 @@ invalid: ...@@ -1179,7 +1179,7 @@ invalid:
/* PFADD var ele ele ele ... ele => :0 or :1 */ /* PFADD var ele ele ele ... ele => :0 or :1 */
void pfaddCommand(client *c) { void pfaddCommand(client *c) {
robj *o = lookupKeyWrite(c->db,c->argv[1]); robj *o = lookupKeyWrite(c->db,c->argv[1],NULL);
struct hllhdr *hdr; struct hllhdr *hdr;
int updated = 0, j; int updated = 0, j;
...@@ -1238,7 +1238,7 @@ void pfcountCommand(client *c) { ...@@ -1238,7 +1238,7 @@ void pfcountCommand(client *c) {
registers = max + HLL_HDR_SIZE; registers = max + HLL_HDR_SIZE;
for (j = 1; j < c->argc; j++) { for (j = 1; j < c->argc; j++) {
/* Check type and size. */ /* Check type and size. */
robj *o = lookupKeyRead(c->db,c->argv[j]); robj *o = lookupKeyRead(c->db,c->argv[j],NULL);
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/ if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
if (isHLLObjectOrReply(c,o) != C_OK) return; if (isHLLObjectOrReply(c,o) != C_OK) return;
...@@ -1259,7 +1259,7 @@ void pfcountCommand(client *c) { ...@@ -1259,7 +1259,7 @@ void pfcountCommand(client *c) {
* *
* The user specified a single key. Either return the cached value * The user specified a single key. Either return the cached value
* or compute one and update the cache. */ * or compute one and update the cache. */
o = lookupKeyWrite(c->db,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1],NULL);
if (o == NULL) { if (o == NULL) {
/* No key? Cardinality is zero since no element was added, otherwise /* No key? Cardinality is zero since no element was added, otherwise
* we would have a key as HLLADD creates it as a side effect. */ * we would have a key as HLLADD creates it as a side effect. */
...@@ -1320,7 +1320,7 @@ void pfmergeCommand(client *c) { ...@@ -1320,7 +1320,7 @@ void pfmergeCommand(client *c) {
memset(max,0,sizeof(max)); memset(max,0,sizeof(max));
for (j = 1; j < c->argc; j++) { for (j = 1; j < c->argc; j++) {
/* Check type and size. */ /* Check type and size. */
robj *o = lookupKeyRead(c->db,c->argv[j]); robj *o = lookupKeyRead(c->db,c->argv[j],NULL);
if (o == NULL) continue; /* Assume empty HLL for non existing var. */ if (o == NULL) continue; /* Assume empty HLL for non existing var. */
if (isHLLObjectOrReply(c,o) != C_OK) return; if (isHLLObjectOrReply(c,o) != C_OK) return;
...@@ -1338,7 +1338,7 @@ void pfmergeCommand(client *c) { ...@@ -1338,7 +1338,7 @@ void pfmergeCommand(client *c) {
} }
/* Create / unshare the destination key's value if needed. */ /* Create / unshare the destination key's value if needed. */
robj *o = lookupKeyWrite(c->db,c->argv[1]); robj *o = lookupKeyWrite(c->db,c->argv[1],NULL);
if (o == NULL) { if (o == NULL) {
/* Create the key with a string value of the exact length to /* Create the key with a string value of the exact length to
* hold our HLL data structure. sdsnewlen() when NULL is passed * hold our HLL data structure. sdsnewlen() when NULL is passed
...@@ -1497,7 +1497,7 @@ void pfdebugCommand(client *c) { ...@@ -1497,7 +1497,7 @@ void pfdebugCommand(client *c) {
robj *o; robj *o;
int j; int j;
o = lookupKeyWrite(c->db,c->argv[2]); o = lookupKeyWrite(c->db,c->argv[2],NULL);
if (o == NULL) { if (o == NULL) {
addReplyError(c,"The specified key does not exist"); addReplyError(c,"The specified key does not exist");
return; return;
......
...@@ -48,10 +48,12 @@ uint64_t dictStringHash(const void *key) { ...@@ -48,10 +48,12 @@ uint64_t dictStringHash(const void *key) {
void dictVanillaFree(void *privdata, void *val); void dictVanillaFree(void *privdata, void *val);
dictType latencyTimeSeriesDictType = { dictType latencyTimeSeriesDictType = {
dictStringHash, /* hash function */ dictStringHash, /* lookup hash function */
dictStringHash, /* stored hash function */
NULL, /* key dup */ NULL, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
dictStringKeyCompare, /* key compare */ dictStringKeyCompare, /* lookup key compare */
dictStringKeyCompare, /* stored key compare */
dictVanillaFree, /* key destructor */ dictVanillaFree, /* key destructor */
dictVanillaFree /* val destructor */ dictVanillaFree /* val destructor */
}; };
......
...@@ -51,19 +51,19 @@ size_t lazyfreeGetFreeEffort(robj *obj) { ...@@ -51,19 +51,19 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
* a lazy free list instead of being freed synchronously. The lazy free list * a lazy free list instead of being freed synchronously. The lazy free list
* will be reclaimed in a different bio.c thread. */ * will be reclaimed in a different bio.c thread. */
#define LAZYFREE_THRESHOLD 64 #define LAZYFREE_THRESHOLD 64
int dbAsyncDelete(redisDb *db, robj *key) { int dbAsyncDelete(redisDb *db, robj *keyname) {
/* 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 /* 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 * is actually just slower... So under a certain limit we just free
* the object synchronously. */ * the object synchronously. */
dictEntry *de = dictUnlink(db->dict,key->ptr); dictEntry *de = dictUnlink(db->dict,keyname->ptr);
if (de) { if (de) {
rkey *key = dictGetKey(de);
robj *val = dictGetVal(de); robj *val = dictGetVal(de);
size_t free_effort = lazyfreeGetFreeEffort(val); size_t free_effort = lazyfreeGetFreeEffort(val);
/* Remove the entry from the expire tree since this is fast anyway. */
if (key->flags & KEY_FLAG_EXPIRE) removeExpireFromTree(db,key);
/* If releasing the object is too much work, do it in the background /* If releasing the object is too much work, do it in the background
* by adding the object to the lazy free list. * by adding the object to the lazy free list.
* Note that if the object is shared, to reclaim it now it is not * Note that if the object is shared, to reclaim it now it is not
...@@ -83,7 +83,7 @@ int dbAsyncDelete(redisDb *db, robj *key) { ...@@ -83,7 +83,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
* field to NULL in order to lazy free it later. */ * field to NULL in order to lazy free it later. */
if (de) { if (de) {
dictFreeUnlinkedEntry(db->dict,de); dictFreeUnlinkedEntry(db->dict,de);
if (server.cluster_enabled) slotToKeyDel(key); if (server.cluster_enabled) slotToKeyDel(keyname);
return 1; return 1;
} else { } else {
return 0; return 0;
...@@ -105,11 +105,11 @@ void freeObjAsync(robj *o) { ...@@ -105,11 +105,11 @@ void freeObjAsync(robj *o) {
* create a new empty set of hash tables and scheduling the old ones for * create a new empty set of hash tables and scheduling the old ones for
* lazy freeing. */ * lazy freeing. */
void emptyDbAsync(redisDb *db) { void emptyDbAsync(redisDb *db) {
dict *oldht1 = db->dict, *oldht2 = db->expires; void *hashtable = db->dict, *tree = db->expires;
db->dict = dictCreate(&dbDictType,NULL); db->dict = dictCreate(&dbDictType,NULL);
db->expires = dictCreate(&keyptrDictType,NULL); db->expires = raxNew();
atomicIncr(lazyfree_objects,dictSize(oldht1)); atomicIncr(lazyfree_objects,dictSize((dict*)hashtable));
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2); bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,hashtable,tree);
} }
/* Empty the slots-keys map of Redis CLuster by creating a new empty one /* Empty the slots-keys map of Redis CLuster by creating a new empty one
...@@ -136,10 +136,10 @@ void lazyfreeFreeObjectFromBioThread(robj *o) { ...@@ -136,10 +136,10 @@ void lazyfreeFreeObjectFromBioThread(robj *o) {
* when the database was logically deleted. 'sl' is a skiplist used by * when the database was logically deleted. 'sl' is a skiplist used by
* Redis Cluster in order to take the hash slots -> keys mapping. This * Redis Cluster in order to take the hash slots -> keys mapping. This
* may be NULL if Redis Cluster is disabled. */ * may be NULL if Redis Cluster is disabled. */
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) { void lazyfreeFreeDatabaseFromBioThread(dict *hashtable, rax *tree) {
size_t numkeys = dictSize(ht1); size_t numkeys = dictSize(hashtable);
dictRelease(ht1); dictRelease(hashtable);
dictRelease(ht2); raxFree(tree);
atomicDecr(lazyfree_objects,numkeys); atomicDecr(lazyfree_objects,numkeys);
} }
......
...@@ -148,7 +148,8 @@ typedef struct RedisModuleCtx RedisModuleCtx; ...@@ -148,7 +148,8 @@ typedef struct RedisModuleCtx RedisModuleCtx;
struct RedisModuleKey { struct RedisModuleKey {
RedisModuleCtx *ctx; RedisModuleCtx *ctx;
redisDb *db; redisDb *db;
robj *key; /* Key name object. */ rkey *keyobj; /* Key object stored at the dictionary. */
robj *keyname; /* Key name object. */
robj *value; /* Value object, or NULL if the key was not found. */ robj *value; /* Value object, or NULL if the key was not found. */
void *iter; /* Iterator. */ void *iter; /* Iterator. */
int mode; /* Opening mode. */ int mode; /* Opening mode. */
...@@ -436,7 +437,7 @@ int moduleCreateEmptyKey(RedisModuleKey *key, int type) { ...@@ -436,7 +437,7 @@ int moduleCreateEmptyKey(RedisModuleKey *key, int type) {
break; break;
default: return REDISMODULE_ERR; default: return REDISMODULE_ERR;
} }
dbAdd(key->db,key->key,obj); key->keyobj = dbAdd(key->db,key->keyname,obj);
key->value = obj; key->value = obj;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -465,7 +466,7 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) { ...@@ -465,7 +466,7 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
} }
if (isempty) { if (isempty) {
dbDelete(key->db,key->key); dbDelete(key->db,key->keyname);
key->value = NULL; key->value = NULL;
return 1; return 1;
} else { } else {
...@@ -1519,11 +1520,12 @@ int RM_SelectDb(RedisModuleCtx *ctx, int newid) { ...@@ -1519,11 +1520,12 @@ int RM_SelectDb(RedisModuleCtx *ctx, int newid) {
void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) { void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
RedisModuleKey *kp; RedisModuleKey *kp;
robj *value; robj *value;
rkey *keyobj;
if (mode & REDISMODULE_WRITE) { if (mode & REDISMODULE_WRITE) {
value = lookupKeyWrite(ctx->client->db,keyname); value = lookupKeyWrite(ctx->client->db,keyname,&keyobj);
} else { } else {
value = lookupKeyRead(ctx->client->db,keyname); value = lookupKeyRead(ctx->client->db,keyname,&keyobj);
if (value == NULL) { if (value == NULL) {
return NULL; return NULL;
} }
...@@ -1533,8 +1535,9 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) { ...@@ -1533,8 +1535,9 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
kp = zmalloc(sizeof(*kp)); kp = zmalloc(sizeof(*kp));
kp->ctx = ctx; kp->ctx = ctx;
kp->db = ctx->client->db; kp->db = ctx->client->db;
kp->key = keyname; kp->keyname = keyname;
incrRefCount(keyname); incrRefCount(keyname);
kp->keyobj = keyobj;
kp->value = value; kp->value = value;
kp->iter = NULL; kp->iter = NULL;
kp->mode = mode; kp->mode = mode;
...@@ -1546,10 +1549,10 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) { ...@@ -1546,10 +1549,10 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
/* Close a key handle. */ /* Close a key handle. */
void RM_CloseKey(RedisModuleKey *key) { void RM_CloseKey(RedisModuleKey *key) {
if (key == NULL) return; if (key == NULL) return;
if (key->mode & REDISMODULE_WRITE) signalModifiedKey(key->db,key->key); if (key->mode & REDISMODULE_WRITE) signalModifiedKey(key->db,key->keyname);
/* TODO: if (key->iter) RM_KeyIteratorStop(kp); */ /* TODO: if (key->iter) RM_KeyIteratorStop(kp); */
RM_ZsetRangeStop(key); RM_ZsetRangeStop(key);
decrRefCount(key->key); decrRefCount(key->keyname);
autoMemoryFreed(key->ctx,REDISMODULE_AM_KEY,key); autoMemoryFreed(key->ctx,REDISMODULE_AM_KEY,key);
zfree(key); zfree(key);
} }
...@@ -1595,13 +1598,13 @@ size_t RM_ValueLength(RedisModuleKey *key) { ...@@ -1595,13 +1598,13 @@ size_t RM_ValueLength(RedisModuleKey *key) {
int RM_DeleteKey(RedisModuleKey *key) { int RM_DeleteKey(RedisModuleKey *key) {
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
if (key->value) { if (key->value) {
dbDelete(key->db,key->key); dbDelete(key->db,key->keyname);
key->value = NULL; key->value = NULL;
} }
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* If the key is open for writing, unlink it (that is delete it in a /* If the key is open for writing, unlink it (that is delete it in a
* non-blocking way, not reclaiming memory immediately) and setup the key to * non-blocking way, not reclaiming memory immediately) and setup the key to
* accept new writes as an empty key (that will be created on demand). * accept new writes as an empty key (that will be created on demand).
* On success REDISMODULE_OK is returned. If the key is not open for * On success REDISMODULE_OK is returned. If the key is not open for
...@@ -1609,7 +1612,7 @@ int RM_DeleteKey(RedisModuleKey *key) { ...@@ -1609,7 +1612,7 @@ int RM_DeleteKey(RedisModuleKey *key) {
int RM_UnlinkKey(RedisModuleKey *key) { int RM_UnlinkKey(RedisModuleKey *key) {
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
if (key->value) { if (key->value) {
dbAsyncDelete(key->db,key->key); dbAsyncDelete(key->db,key->keyname);
key->value = NULL; key->value = NULL;
} }
return REDISMODULE_OK; return REDISMODULE_OK;
...@@ -1619,7 +1622,7 @@ int RM_UnlinkKey(RedisModuleKey *key) { ...@@ -1619,7 +1622,7 @@ int RM_UnlinkKey(RedisModuleKey *key) {
* If no TTL is associated with the key or if the key is empty, * If no TTL is associated with the key or if the key is empty,
* REDISMODULE_NO_EXPIRE is returned. */ * REDISMODULE_NO_EXPIRE is returned. */
mstime_t RM_GetExpire(RedisModuleKey *key) { mstime_t RM_GetExpire(RedisModuleKey *key) {
mstime_t expire = getExpire(key->db,key->key); mstime_t expire = getExpire(key->keyobj);
if (expire == -1 || key->value == NULL) return -1; if (expire == -1 || key->value == NULL) return -1;
expire -= mstime(); expire -= mstime();
return expire >= 0 ? expire : 0; return expire >= 0 ? expire : 0;
...@@ -1639,9 +1642,9 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) { ...@@ -1639,9 +1642,9 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (expire != REDISMODULE_NO_EXPIRE) { if (expire != REDISMODULE_NO_EXPIRE) {
expire += mstime(); expire += mstime();
setExpire(key->ctx->client,key->db,key->key,expire); setExpire(key->ctx->client,key->db,key->keyobj,expire);
} else { } else {
removeExpire(key->db,key->key); removeExpire(key->db,key->keyobj);
} }
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -1657,7 +1660,7 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) { ...@@ -1657,7 +1660,7 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) { int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) {
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR; if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
RM_DeleteKey(key); RM_DeleteKey(key);
setKey(key->db,key->key,str); setKey(key->db,key->keyname,str);
key->value = str; key->value = str;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -1707,7 +1710,7 @@ char *RM_StringDMA(RedisModuleKey *key, size_t *len, int mode) { ...@@ -1707,7 +1710,7 @@ char *RM_StringDMA(RedisModuleKey *key, size_t *len, int mode) {
/* For write access, and even for read access if the object is encoded, /* For write access, and even for read access if the object is encoded,
* we unshare the string (that has the side effect of decoding it). */ * we unshare the string (that has the side effect of decoding it). */
if ((mode & REDISMODULE_WRITE) || key->value->encoding != OBJ_ENCODING_RAW) if ((mode & REDISMODULE_WRITE) || key->value->encoding != OBJ_ENCODING_RAW)
key->value = dbUnshareStringValue(key->db, key->key, key->value); key->value = dbUnshareStringValue(key->db, key->keyname, key->value);
*len = sdslen(key->value->ptr); *len = sdslen(key->value->ptr);
return key->value->ptr; return key->value->ptr;
...@@ -1737,12 +1740,12 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) { ...@@ -1737,12 +1740,12 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
if (key->value == NULL) { if (key->value == NULL) {
/* Empty key: create it with the new size. */ /* Empty key: create it with the new size. */
robj *o = createObject(OBJ_STRING,sdsnewlen(NULL, newlen)); robj *o = createObject(OBJ_STRING,sdsnewlen(NULL, newlen));
setKey(key->db,key->key,o); setKey(key->db,key->keyname,o);
key->value = o; key->value = o;
decrRefCount(o); decrRefCount(o);
} else { } else {
/* Unshare and resize. */ /* Unshare and resize. */
key->value = dbUnshareStringValue(key->db, key->key, key->value); key->value = dbUnshareStringValue(key->db, key->keyname, key->value);
size_t curlen = sdslen(key->value->ptr); size_t curlen = sdslen(key->value->ptr);
if (newlen > curlen) { if (newlen > curlen) {
key->value->ptr = sdsgrowzero(key->value->ptr,newlen); key->value->ptr = sdsgrowzero(key->value->ptr,newlen);
...@@ -3088,7 +3091,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) { ...@@ -3088,7 +3091,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) {
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR; if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
RM_DeleteKey(key); RM_DeleteKey(key);
robj *o = createModuleObject(mt,value); robj *o = createModuleObject(mt,value);
setKey(key->db,key->key,o); setKey(key->db,key->keyname,o);
decrRefCount(o); decrRefCount(o);
key->value = o; key->value = o;
return REDISMODULE_OK; return REDISMODULE_OK;
...@@ -5031,7 +5034,8 @@ int dictCStringKeyCompare(void *privdata, const void *key1, const void *key2) { ...@@ -5031,7 +5034,8 @@ int dictCStringKeyCompare(void *privdata, const void *key1, const void *key2) {
} }
dictType moduleAPIDictType = { dictType moduleAPIDictType = {
dictCStringKeyHash, /* hash function */ dictCStringKeyHash, /* lookup hash function */
dictCStringKeyHash, /* stored hash function */
NULL, /* key dup */ NULL, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
dictCStringKeyCompare, /* key compare */ dictCStringKeyCompare, /* key compare */
......
...@@ -44,14 +44,6 @@ robj *createObject(int type, void *ptr) { ...@@ -44,14 +44,6 @@ robj *createObject(int type, void *ptr) {
o->encoding = OBJ_ENCODING_RAW; o->encoding = OBJ_ENCODING_RAW;
o->ptr = ptr; o->ptr = ptr;
o->refcount = 1; o->refcount = 1;
/* 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; return o;
} }
...@@ -89,11 +81,6 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) { ...@@ -89,11 +81,6 @@ 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();
}
sh->len = len; sh->len = len;
sh->alloc = len; sh->alloc = len;
...@@ -1052,8 +1039,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) { ...@@ -1052,8 +1039,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mh->db[mh->num_dbs].overhead_ht_main = mem; mh->db[mh->num_dbs].overhead_ht_main = mem;
mem_total+=mem; mem_total+=mem;
mem = dictSize(db->expires) * sizeof(dictEntry) + #warning "Fix the memory computation here with expires overhead"
dictSlots(db->expires) * sizeof(dictEntry*);
mh->db[mh->num_dbs].overhead_ht_expires = mem; mh->db[mh->num_dbs].overhead_ht_expires = mem;
mem_total+=mem; mem_total+=mem;
...@@ -1202,12 +1188,12 @@ sds getMemoryDoctorReport(void) { ...@@ -1202,12 +1188,12 @@ sds getMemoryDoctorReport(void) {
* The lru_idle and lru_clock args are only relevant if policy * The lru_idle and lru_clock args are only relevant if policy
* is MAXMEMORY_FLAG_LRU. * is MAXMEMORY_FLAG_LRU.
* Either or both of them may be <0, in that case, nothing is set. */ * Either or both of them may be <0, in that case, nothing is set. */
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle, void objectSetLRUOrLFU(rkey *key, long long lfu_freq, long long lru_idle,
long long lru_clock) { long long lru_clock) {
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
if (lfu_freq >= 0) { if (lfu_freq >= 0) {
serverAssert(lfu_freq <= 255); serverAssert(lfu_freq <= 255);
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq; key->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
} }
} else if (lru_idle >= 0) { } else if (lru_idle >= 0) {
/* Provided LRU idle time is in seconds. Scale /* Provided LRU idle time is in seconds. Scale
...@@ -1223,7 +1209,7 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle, ...@@ -1223,7 +1209,7 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
* some time. */ * some time. */
if (lru_abs < 0) if (lru_abs < 0)
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX; lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
val->lru = lru_abs; key->lru = lru_abs;
} }
} }
...@@ -1231,15 +1217,16 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle, ...@@ -1231,15 +1217,16 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
/* This is a helper function for the OBJECT command. We need to lookup keys /* This is a helper function for the OBJECT command. We need to lookup keys
* without any modification of LRU or other parameters. */ * without any modification of LRU or other parameters. */
robj *objectCommandLookup(client *c, robj *key) { robj *objectCommandLookup(client *c, robj *keyname, rkey **key) {
dictEntry *de; dictEntry *de;
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL; if ((de = dictFind(c->db->dict,keyname->ptr)) == NULL) return NULL;
if (key) *key = dictGetKey(de);
return (robj*) dictGetVal(de); return (robj*) dictGetVal(de);
} }
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) { robj *objectCommandLookupOrReply(client *c, robj *keyname, rkey **key, robj *reply) {
robj *o = objectCommandLookup(c,key); robj *o = objectCommandLookup(c,keyname,key);
if (!o) addReply(c, reply); if (!o) addReply(c, reply);
return o; return o;
...@@ -1260,24 +1247,26 @@ NULL ...@@ -1260,24 +1247,26 @@ NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp])) if ((o = objectCommandLookupOrReply(c,c->argv[2],NULL,
== NULL) return; shared.null[c->resp])) == NULL) return;
addReplyLongLong(c,o->refcount); addReplyLongLong(c,o->refcount);
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp])) if ((o = objectCommandLookupOrReply(c,c->argv[2],NULL,
== NULL) return; shared.null[c->resp])) == NULL) return;
addReplyBulkCString(c,strEncoding(o->encoding)); addReplyBulkCString(c,strEncoding(o->encoding));
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp])) rkey *key;
== NULL) return; if ((o = objectCommandLookupOrReply(c,c->argv[2],&key,
shared.null[c->resp])) == NULL) return;
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust."); addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
return; return;
} }
addReplyLongLong(c,estimateObjectIdleTime(o)/1000); addReplyLongLong(c,estimateObjectIdleTime(key)/1000);
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp])) rkey *k;
== NULL) return; if ((o = objectCommandLookupOrReply(c,c->argv[2],&k,
shared.null[c->resp])) == NULL) return;
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) { if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust."); addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
return; return;
...@@ -1286,7 +1275,7 @@ NULL ...@@ -1286,7 +1275,7 @@ NULL
* in case of the key has not been accessed for a long time, * in case of the key has not been accessed for a long time,
* because we update the access time only * because we update the access time only
* when the key is read or overwritten. */ * when the key is read or overwritten. */
addReplyLongLong(c,LFUDecrAndReturn(o)); addReplyLongLong(c,LFUDecrAndReturn(k));
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);
} }
......
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