mirror of https://mirror.osredm.com/root/redis.git
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: debing.sun <debing.sun@redis.com>
This commit is contained in:
parent
de7f2f87f7
commit
05aed4cab9
52
src/db.c
52
src/db.c
|
@ -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]);
|
||||
|
|
Loading…
Reference in New Issue