Unverified Commit 05aed4ca authored by Filipe Oliveira (Redis)'s avatar Filipe Oliveira (Redis) Committed by GitHub
Browse files

Optimize SET/INCR*/DECR*/SETRANGE/APPEND by reducing duplicate computation (#13505)



- Avoid addReplyLongLong (which converts back to string) the value we
already have as a robj, by using addReplyProto + addReply
- Avoid doing dbFind Twice for the same dictEntry on
INCR*/DECR*/SETRANGE/APPEND commands.
- Avoid multiple sdslen calls with the same input on setrangeCommand and
appendCommand
- Introduce setKeyWithDictEntry, which is like setKey(), but accepts an
optional dictEntry input: Avoids the second dictFind in SET command

---------
Co-authored-by: default avatardebing.sun <debing.sun@redis.com>
parent de7f2f87
......@@ -49,6 +49,9 @@ void updateLFU(robj *val) {
* found in the specified DB. This function implements the functionality of
* lookupKeyRead(), lookupKeyWrite() and their ...WithFlags() variants.
*
* 'deref' is an optional output dictEntry reference argument, to get the
* associated dictEntry* of the key in case the key is found.
*
* Side-effects of calling this function:
*
* 1. A key gets expired if it reached it's TTL.
......@@ -72,7 +75,7 @@ void updateLFU(robj *val) {
* Even if the key expiry is master-driven, we can correctly report a key is
* expired on replicas even if the master is lagging expiring our key via DELs
* in the replication link. */
robj *lookupKey(redisDb *db, robj *key, int flags) {
robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) {
dictEntry *de = dbFind(db, key->ptr);
robj *val = NULL;
if (de) {
......@@ -123,6 +126,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* TODO: Use separate misses stats and notify event for WRITE */
}
if (val && deref) *deref = de;
return val;
}
......@@ -137,7 +141,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* the key. */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
serverAssert(!(flags & LOOKUP_WRITE));
return lookupKey(db, key, flags);
return lookupKey(db, key, flags, NULL);
}
/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
......@@ -153,13 +157,20 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
* Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
return lookupKey(db, key, flags | LOOKUP_WRITE);
return lookupKey(db, key, flags | LOOKUP_WRITE, NULL);
}
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
}
/* Like lookupKeyWrite(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the dbFind call.
*/
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref) {
return lookupKey(db, key, LOOKUP_NONE | LOOKUP_WRITE, deref);
}
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReplyOrErrorObject(c, reply);
......@@ -294,6 +305,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
dbSetValue(db, key, val, 0, NULL);
}
/* Replace an existing key with a new value, we just replace value and don't
* emit any events.
* The dictEntry input is optional, can be used if we already have one.
*/
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de) {
dbSetValue(db, key, val, 0, de);
}
/* High level Set operation. This function can be used in order to set
* a key, whatever it was existing or not, to a new object.
*
......@@ -308,6 +327,12 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
* The client 'c' argument may be set to NULL if the operation is performed
* in a context where there is no clear client performing the operation. */
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
setKeyWithDictEntry(c,db,key,val,flags,NULL);
}
/* Like setKey(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the dictFind call. */
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de) {
int keyfound = 0;
if (flags & SETKEY_ALREADY_EXIST)
......@@ -322,7 +347,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
} else if (keyfound<0) {
dbAddInternal(db,key,val,1);
} else {
dbSetValue(db,key,val,1,NULL);
dbSetValue(db,key,val,1,de);
}
incrRefCount(val);
if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key);
......@@ -452,12 +477,18 @@ int dbDelete(redisDb *db, robj *key) {
* using an sdscat() call to append some data, or anything else.
*/
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
return dbUnshareStringValueWithDictEntry(db,key,o,NULL);
}
/* Like dbUnshareStringValue(), but accepts a optional dictEntry,
* which can be used if we already have one, thus saving the dbFind call. */
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de) {
serverAssert(o->type == OBJ_STRING);
if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplaceValue(db,key,o);
dbReplaceValueWithDictEntry(db,key,o,de);
}
return o;
}
......@@ -1828,16 +1859,23 @@ int removeExpire(redisDb *db, robj *key) {
return kvstoreDictDelete(db->expires, getKeySlot(key->ptr), 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, *existing;
setExpireWithDictEntry(c,db,key,when,NULL);
}
/* Like setExpire(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the kvstoreDictFind call. */
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde) {
dictEntry *de, *existing;
/* Reuse the sds from the main dict in the expire dict */
int slot = getKeySlot(key->ptr);
kde = kvstoreDictFind(db->keys, slot, key->ptr);
if (!kde) kde = kvstoreDictFind(db->keys, slot, key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
de = kvstoreDictAddRaw(db->expires, slot, dictGetKey(kde), &existing);
if (existing) {
......
......@@ -970,6 +970,12 @@ void addReplyLongLong(client *c, long long ll) {
}
}
void addReplyLongLongFromStr(client *c, robj *str) {
addReplyProto(c,":",1);
addReply(c,str);
addReplyProto(c,"\r\n",2);
}
void addReplyAggregateLen(client *c, long length, int prefix) {
serverAssert(length >= 0);
if (prepareClientToWrite(c) != C_OK) return;
......
......@@ -2631,6 +2631,7 @@ void addReplyDouble(client *c, double d);
void addReplyBigNum(client *c, const char *num, size_t len);
void addReplyHumanLongDouble(client *c, long double d);
void addReplyLongLong(client *c, long long ll);
void addReplyLongLongFromStr(client *c, robj* str);
void addReplyArrayLen(client *c, long length);
void addReplyMapLen(client *c, long length);
void addReplySetLen(client *c, long length);
......@@ -3366,10 +3367,12 @@ void propagateDeletion(redisDb *db, robj *key, int lazy);
int keyIsExpired(redisDb *db, robj *key);
long long getExpire(redisDb *db, robj *key);
void setExpire(client *c, redisDb *db, robj *key, long long when);
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde);
int checkAlreadyExpired(long long when);
int parseExtendedExpireArgumentsOrReply(client *c, int *flags);
robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key);
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref);
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
......@@ -3389,6 +3392,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
dictEntry *dbAdd(redisDb *db, robj *key, robj *val);
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
void dbReplaceValue(redisDb *db, robj *key, robj *val);
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de);
#define SETKEY_KEEPTTL 1
#define SETKEY_NO_SIGNAL 2
......@@ -3396,11 +3400,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val);
#define SETKEY_DOESNT_EXIST 8
#define SETKEY_ADD_OR_UPDATE 16 /* Key most likely doesn't exists */
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags);
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de);
robj *dbRandomKey(redisDb *db);
int dbGenericDelete(redisDb *db, robj *key, int async, int flags);
int dbSyncDelete(redisDb *db, robj *key);
int dbDelete(redisDb *db, robj *key);
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de);
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
......
......@@ -73,7 +73,8 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
if (getGenericCommand(c) == C_ERR) return;
}
found = (lookupKeyWrite(c->db,key) != NULL);
dictEntry *de = NULL;
found = (lookupKeyWriteWithDictEntry(c->db,key,&de) != NULL);
if ((flags & OBJ_SET_NX && found) ||
(flags & OBJ_SET_XX && !found))
......@@ -88,12 +89,12 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
setkey_flags |= ((flags & OBJ_KEEPTTL) || expire) ? SETKEY_KEEPTTL : 0;
setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST;
setKey(c,c->db,key,val,setkey_flags);
setKeyWithDictEntry(c,c->db,key,val,setkey_flags,de);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
if (expire) {
setExpire(c,c->db,key,milliseconds);
setExpireWithDictEntry(c,c->db,key,milliseconds,de);
/* Propagate as SET Key Value PXAT millisecond-timestamp if there is
* EX/PX/EXAT flag. */
if (!(flags & OBJ_PXAT)) {
......@@ -422,6 +423,7 @@ void setrangeCommand(client *c) {
robj *o;
long offset;
sds value = c->argv[3]->ptr;
const size_t value_len = sdslen(value);
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
return;
......@@ -431,19 +433,20 @@ void setrangeCommand(client *c) {
return;
}
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (o == NULL) {
/* Return 0 when setting nothing on a non-existing string */
if (sdslen(value) == 0) {
if (value_len == 0) {
addReply(c,shared.czero);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
if (checkStringLength(c,offset,value_len) != C_OK)
return;
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+value_len));
dbAdd(c->db,c->argv[1],o);
} else {
size_t olen;
......@@ -454,22 +457,22 @@ void setrangeCommand(client *c) {
/* Return existing string length when setting nothing */
olen = stringObjectLen(o);
if (sdslen(value) == 0) {
if (value_len == 0) {
addReplyLongLong(c,olen);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
if (checkStringLength(c,offset,value_len) != C_OK)
return;
/* Create a copy when the object is shared or encoded. */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
}
if (sdslen(value) > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
memcpy((char*)o->ptr+offset,value,sdslen(value));
if (value_len > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+value_len);
memcpy((char*)o->ptr+offset,value,value_len);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,
"setrange",c->argv[1],c->db->id);
......@@ -571,8 +574,8 @@ void msetnxCommand(client *c) {
void incrDecrCommand(client *c, long long incr) {
long long value, oldvalue;
robj *o, *new;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (checkType(c,o,OBJ_STRING)) return;
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
......@@ -593,7 +596,7 @@ void incrDecrCommand(client *c, long long incr) {
} else {
new = createStringObjectFromLongLongForValue(value);
if (o) {
dbReplaceValue(c->db,c->argv[1],new);
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
} else {
dbAdd(c->db,c->argv[1],new);
}
......@@ -601,7 +604,7 @@ void incrDecrCommand(client *c, long long incr) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReplyLongLong(c, value);
addReplyLongLongFromStr(c,new);
}
void incrCommand(client *c) {
......@@ -635,7 +638,8 @@ void incrbyfloatCommand(client *c) {
long double incr, value;
robj *o, *new;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (checkType(c,o,OBJ_STRING)) return;
if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
......@@ -648,7 +652,7 @@ void incrbyfloatCommand(client *c) {
}
new = createStringObjectFromLongDouble(value,1);
if (o)
dbReplaceValue(c->db,c->argv[1],new);
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
else
dbAdd(c->db,c->argv[1],new);
signalModifiedKey(c,c->db,c->argv[1]);
......@@ -668,7 +672,8 @@ void appendCommand(client *c) {
size_t totlen;
robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (o == NULL) {
/* Create the key */
c->argv[2] = tryObjectEncoding(c->argv[2]);
......@@ -682,12 +687,13 @@ void appendCommand(client *c) {
/* "append" is an argument, so always an sds */
append = c->argv[2];
if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK)
const size_t append_len = sdslen(append->ptr);
if (checkStringLength(c,stringObjectLen(o),append_len) != C_OK)
return;
/* Append the value */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
o->ptr = sdscatlen(o->ptr,append->ptr,append_len);
totlen = sdslen(o->ptr);
}
signalModifiedKey(c,c->db,c->argv[1]);
......
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