Optimize MSETNX to avoid double lookup (#11944)

This is a redo of #11594 which got reverted in #11940
It improves performance by avoiding double lookup of the the key.
This commit is contained in:
Oran Agra 2023-05-28 10:58:29 +03:00 committed by GitHub
parent 6117f28822
commit 2764dc3768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 10 deletions

View File

@ -47,6 +47,7 @@
int expireIfNeeded(redisDb *db, robj *key, int flags); int expireIfNeeded(redisDb *db, robj *key, int flags);
int keyIsExpired(redisDb *db, robj *key); int keyIsExpired(redisDb *db, robj *key);
static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de);
/* 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.
@ -187,11 +188,17 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
/* 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. */ * If the update_if_existing argument is false, the the program is aborted
void dbAdd(redisDb *db, robj *key, robj *val) { * if the key already exists, otherwise, it can fall back to dbOverwite. */
sds copy = sdsdup(key->ptr); static void dbAddInternal(redisDb *db, robj *key, robj *val, int update_if_existing) {
dictEntry *de = dictAddRaw(db->dict, copy, NULL); dictEntry *existing;
dictEntry *de = dictAddRaw(db->dict, key->ptr, &existing);
if (update_if_existing && existing) {
dbSetValue(db, key, val, 1, existing);
return;
}
serverAssertWithInfo(NULL, key, de != NULL); serverAssertWithInfo(NULL, key, de != NULL);
dictSetKey(db->dict, de, sdsdup(key->ptr));
initObjectLRUOrLFU(val); initObjectLRUOrLFU(val);
dictSetVal(db->dict, de, val); dictSetVal(db->dict, de, val);
signalKeyAsReady(db, key, val->type); signalKeyAsReady(db, key, val->type);
@ -199,6 +206,10 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
notifyKeyspaceEvent(NOTIFY_NEW,"new",key,db->id); notifyKeyspaceEvent(NOTIFY_NEW,"new",key,db->id);
} }
void dbAdd(redisDb *db, robj *key, robj *val) {
dbAddInternal(db, key, val, 0);
}
/* This is a special version of dbAdd() that is used only when loading /* This is a special version of dbAdd() that is used only when loading
* keys from the RDB file: the key is passed as an SDS string that is * keys from the RDB file: the key is passed as an SDS string that is
* retained by the function (and not freed by the caller). * retained by the function (and not freed by the caller).
@ -228,10 +239,11 @@ int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
* replacement (in which case we need to emit deletion signals), or just an * replacement (in which case we need to emit deletion signals), or just an
* update of a value of an existing key (when false). * update of a value of an existing key (when false).
* *
* The dictEntry input is optional, can be used if we already have one.
*
* The program is aborted if the key was not already present. */ * The program is aborted if the key was not already present. */
static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite) { static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de) {
dictEntry *de = dictFind(db->dict,key->ptr); if (!de) de = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,de != NULL); serverAssertWithInfo(NULL,key,de != NULL);
robj *old = dictGetVal(de); robj *old = dictGetVal(de);
@ -264,7 +276,7 @@ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite) {
/* Replace an existing key with a new value, we just replace value and don't /* Replace an existing key with a new value, we just replace value and don't
* emit any events */ * emit any events */
void dbReplaceValue(redisDb *db, robj *key, robj *val) { void dbReplaceValue(redisDb *db, robj *key, robj *val) {
dbSetValue(db, key, val, 0); dbSetValue(db, key, val, 0, NULL);
} }
/* 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
@ -285,13 +297,17 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
if (flags & SETKEY_ALREADY_EXIST) if (flags & SETKEY_ALREADY_EXIST)
keyfound = 1; keyfound = 1;
else if (flags & SETKEY_ADD_OR_UPDATE)
keyfound = -1;
else if (!(flags & SETKEY_DOESNT_EXIST)) else if (!(flags & SETKEY_DOESNT_EXIST))
keyfound = (lookupKeyWrite(db,key) != NULL); keyfound = (lookupKeyWrite(db,key) != NULL);
if (!keyfound) { if (!keyfound) {
dbAdd(db,key,val); dbAdd(db,key,val);
} else if (keyfound<0) {
dbAddInternal(db,key,val,1);
} else { } else {
dbSetValue(db,key,val,1); dbSetValue(db,key,val,1,NULL);
} }
incrRefCount(val); incrRefCount(val);
if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key); if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key);

View File

@ -3247,6 +3247,7 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val);
#define SETKEY_NO_SIGNAL 2 #define SETKEY_NO_SIGNAL 2
#define SETKEY_ALREADY_EXIST 4 #define SETKEY_ALREADY_EXIST 4
#define SETKEY_DOESNT_EXIST 8 #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 setKey(client *c, redisDb *db, robj *key, robj *val, int flags);
robj *dbRandomKey(redisDb *db); robj *dbRandomKey(redisDb *db);
int dbGenericDelete(redisDb *db, robj *key, int async, int flags); int dbGenericDelete(redisDb *db, robj *key, int async, int flags);

View File

@ -576,10 +576,14 @@ void msetGenericCommand(client *c, int nx) {
} }
} }
int setkey_flags = nx ? SETKEY_DOESNT_EXIST : 0;
for (j = 1; j < c->argc; j += 2) { for (j = 1; j < c->argc; j += 2) {
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
setKey(c, c->db, c->argv[j], c->argv[j + 1], 0); setKey(c, c->db, c->argv[j], c->argv[j + 1], setkey_flags);
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id); notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
/* In MSETNX, It could be that we're overriding the same key, we can't be sure it doesn't exist. */
if (nx)
setkey_flags = SETKEY_ADD_OR_UPDATE;
} }
server.dirty += (c->argc-1)/2; server.dirty += (c->argc-1)/2;
addReply(c, nx ? shared.cone : shared.ok); addReply(c, nx ? shared.cone : shared.ok);

View File

@ -234,6 +234,11 @@ start_server {tags {"string"}} {
assert_error {*wrong number of arguments for 'msetnx' command} {r msetnx x{t} 20 y{t} "foo bar" z{t}} assert_error {*wrong number of arguments for 'msetnx' command} {r msetnx x{t} 20 y{t} "foo bar" z{t}}
} }
test {MSET with already existing - same key twice} {
r set x{t} x
list [r mset x{t} xxx x{t} yyy] [r get x{t}]
} {OK yyy}
test {MSETNX with already existent key} { test {MSETNX with already existent key} {
list [r msetnx x1{t} xxx y2{t} yyy x{t} 20] [r exists x1{t}] [r exists y2{t}] list [r msetnx x1{t} xxx y2{t} yyy x{t} 20] [r exists x1{t}] [r exists y2{t}]
} {0 0 0} } {0 0 0}