diff --git a/src/db.c b/src/db.c index 9daa5ddb..fea2f12e 100644 --- a/src/db.c +++ b/src/db.c @@ -616,3 +616,76 @@ void persistCommand(redisClient *c) { } } } + +/* ----------------------------------------------------------------------------- + * API to get key arguments from commands + * ---------------------------------------------------------------------------*/ + +int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { + int j, i = 0, last, *keys; + REDIS_NOTUSED(argv); + + if (cmd->firstkey == 0) { + *numkeys = 0; + return NULL; + } + last = cmd->lastkey; + if (last < 0) last = argc+last; + keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); + for (j = cmd->firstkey; j <= last; j += cmd->keystep) { + redisAssert(j < argc); + keys[i++] = j; + } + *numkeys = i; + return keys; +} + +int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { + if (cmd->getkeys_proc) { + return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); + } else { + return getKeysUsingCommandTable(cmd,argv,argc,numkeys); + } +} + +void getKeysFreeResult(int *result) { + zfree(result); +} + +int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { + if (flags & REDIS_GETKEYS_PRELOAD) { + *numkeys = 0; + return NULL; + } else { + return getKeysUsingCommandTable(cmd,argv,argc,numkeys); + } +} + +int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { + if (flags & REDIS_GETKEYS_PRELOAD) { + int *keys = zmalloc(sizeof(int)); + *numkeys = 1; + keys[0] = 1; + return keys; + } else { + return getKeysUsingCommandTable(cmd,argv,argc,numkeys); + } +} + +int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { + int i, num, *keys; + REDIS_NOTUSED(cmd); + REDIS_NOTUSED(flags); + + num = atoi(argv[2]->ptr); + /* Sanity check. Don't return any key if the command is going to + * reply with syntax error. */ + if (num > (argc-3)) { + *numkeys = 0; + return NULL; + } + keys = zmalloc(sizeof(int)*num); + for (i = 0; i < num; i++) keys[i] = 3+i; + *numkeys = num; + return keys; +} diff --git a/src/dscache.c b/src/dscache.c index cbe9bb01..46300e63 100644 --- a/src/dscache.c +++ b/src/dscache.c @@ -903,59 +903,6 @@ int waitForSwappedKey(redisClient *c, robj *key) { return 1; } -/* Preload keys for any command with first, last and step values for - * the command keys prototype, as defined in the command table. */ -void waitForMultipleSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) { - int j, last; - if (cmd->vm_firstkey == 0) return; - last = cmd->vm_lastkey; - if (last < 0) last = argc+last; - for (j = cmd->vm_firstkey; j <= last; j += cmd->vm_keystep) { - redisAssert(j < argc); - waitForSwappedKey(c,argv[j]); - } -} - -/* Preload keys needed for the ZUNIONSTORE and ZINTERSTORE commands. - * Note that the number of keys to preload is user-defined, so we need to - * apply a sanity check against argc. */ -void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) { - int i, num; - REDIS_NOTUSED(cmd); - - num = atoi(argv[2]->ptr); - if (num > (argc-3)) return; - for (i = 0; i < num; i++) { - waitForSwappedKey(c,argv[3+i]); - } -} - -/* Preload keys needed to execute the entire MULTI/EXEC block. - * - * This function is called by blockClientOnSwappedKeys when EXEC is issued, - * and will block the client when any command requires a swapped out value. */ -void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) { - int i, margc; - struct redisCommand *mcmd; - robj **margv; - REDIS_NOTUSED(cmd); - REDIS_NOTUSED(argc); - REDIS_NOTUSED(argv); - - if (!(c->flags & REDIS_MULTI)) return; - for (i = 0; i < c->mstate.count; i++) { - mcmd = c->mstate.commands[i].cmd; - margc = c->mstate.commands[i].argc; - margv = c->mstate.commands[i].argv; - - if (mcmd->vm_preload_proc != NULL) { - mcmd->vm_preload_proc(c,mcmd,margc,margv); - } else { - waitForMultipleSwappedKeys(c,mcmd,margc,margv); - } - } -} - /* Is this client attempting to run a command against swapped keys? * If so, block it ASAP, load the keys in background, then resume it. * @@ -967,10 +914,39 @@ void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int * Return 1 if the client is marked as blocked, 0 if the client can * continue as the keys it is going to access appear to be in memory. */ int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd) { - if (cmd->vm_preload_proc != NULL) { - cmd->vm_preload_proc(c,cmd,c->argc,c->argv); + int *keyindex, numkeys, j, i; + + /* EXEC is a special case, we need to preload all the commands + * queued into the transaction */ + if (cmd->proc == execCommand) { + struct redisCommand *mcmd; + robj **margv; + int margc; + + if (!(c->flags & REDIS_MULTI)) return 0; + for (i = 0; i < c->mstate.count; i++) { + mcmd = c->mstate.commands[i].cmd; + margc = c->mstate.commands[i].argc; + margv = c->mstate.commands[i].argv; + + keyindex = getKeysFromCommand(mcmd,margv,margc,&numkeys, + REDIS_GETKEYS_PRELOAD); + for (j = 0; j < numkeys; j++) { + redisLog(REDIS_DEBUG,"Preloading %s", + (char*)margv[keyindex[j]]->ptr); + waitForSwappedKey(c,margv[keyindex[j]]); + } + getKeysFreeResult(keyindex); + } } else { - waitForMultipleSwappedKeys(c,cmd,c->argc,c->argv); + keyindex = getKeysFromCommand(cmd,c->argv,c->argc,&numkeys, + REDIS_GETKEYS_PRELOAD); + for (j = 0; j < numkeys; j++) { + redisLog(REDIS_DEBUG,"Preloading %s", + (char*)c->argv[keyindex[j]]->ptr); + waitForSwappedKey(c,c->argv[keyindex[j]]); + } + getKeysFreeResult(keyindex); } /* If the client was blocked for at least one key, mark it as blocked. */ diff --git a/src/redis.c b/src/redis.c index 866ac360..19fd912c 100644 --- a/src/redis.c +++ b/src/redis.c @@ -70,12 +70,12 @@ struct redisServer server; /* server global state */ struct redisCommand *commandTable; struct redisCommand redisCommandTable[] = { {"get",getCommand,2,0,NULL,1,1,1,0,0}, - {"set",setCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0,0,0}, - {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0,0,0}, - {"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0,0,0}, + {"set",setCommand,3,REDIS_CMD_DENYOOM,noPreloadGetKeys,1,1,1,0,0}, + {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,noPreloadGetKeys,1,1,1,0,0}, + {"setex",setexCommand,4,REDIS_CMD_DENYOOM,noPreloadGetKeys,2,2,1,0,0}, {"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1,0,0}, {"strlen",strlenCommand,2,0,NULL,1,1,1,0,0}, - {"del",delCommand,-2,0,NULL,0,0,0,0,0}, + {"del",delCommand,-2,0,noPreloadGetKeys,1,-1,1,0,0}, {"exists",existsCommand,2,0,NULL,1,1,1,0,0}, {"setbit",setbitCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1,0,0}, {"getbit",getbitCommand,3,0,NULL,1,1,1,0,0}, @@ -94,7 +94,7 @@ struct redisCommand redisCommandTable[] = { {"lpop",lpopCommand,2,0,NULL,1,1,1,0,0}, {"brpop",brpopCommand,-3,0,NULL,1,1,1,0,0}, {"brpoplpush",brpoplpushCommand,4,REDIS_CMD_DENYOOM,NULL,1,2,1,0,0}, - {"blpop",blpopCommand,-3,0,NULL,1,1,1,0,0}, + {"blpop",blpopCommand,-3,0,NULL,1,-2,1,0,0}, {"llen",llenCommand,2,0,NULL,1,1,1,0,0}, {"lindex",lindexCommand,3,0,NULL,1,1,1,0,0}, {"lset",lsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1,0,0}, @@ -121,8 +121,8 @@ struct redisCommand redisCommandTable[] = { {"zrem",zremCommand,3,0,NULL,1,1,1,0,0}, {"zremrangebyscore",zremrangebyscoreCommand,4,0,NULL,1,1,1,0,0}, {"zremrangebyrank",zremrangebyrankCommand,4,0,NULL,1,1,1,0,0}, - {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0,0,0}, - {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0,0,0}, + {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterGetKeys,0,0,0,0,0}, + {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterGetKeys,0,0,0,0,0}, {"zrange",zrangeCommand,-4,0,NULL,1,1,1,0,0}, {"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1,0,0}, {"zrevrangebyscore",zrevrangebyscoreCommand,-4,0,NULL,1,1,1,0,0}, @@ -152,10 +152,10 @@ struct redisCommand redisCommandTable[] = { {"randomkey",randomkeyCommand,1,0,NULL,0,0,0,0,0}, {"select",selectCommand,2,0,NULL,0,0,0,0,0}, {"move",moveCommand,3,0,NULL,1,1,1,0,0}, - {"rename",renameCommand,3,0,NULL,1,1,1,0,0}, - {"renamenx",renamenxCommand,3,0,NULL,1,1,1,0,0}, - {"expire",expireCommand,3,0,NULL,0,0,0,0,0}, - {"expireat",expireatCommand,3,0,NULL,0,0,0,0,0}, + {"rename",renameCommand,3,0,renameGetKeys,1,2,1,0,0}, + {"renamenx",renamenxCommand,3,0,renameGetKeys,1,2,1,0,0}, + {"expire",expireCommand,3,0,NULL,1,1,1,0,0}, + {"expireat",expireatCommand,3,0,NULL,1,1,1,0,0}, {"keys",keysCommand,2,0,NULL,0,0,0,0,0}, {"dbsize",dbsizeCommand,1,0,NULL,0,0,0,0,0}, {"auth",authCommand,2,0,NULL,0,0,0,0,0}, @@ -168,7 +168,7 @@ struct redisCommand redisCommandTable[] = { {"lastsave",lastsaveCommand,1,0,NULL,0,0,0,0,0}, {"type",typeCommand,2,0,NULL,1,1,1,0,0}, {"multi",multiCommand,1,0,NULL,0,0,0,0,0}, - {"exec",execCommand,1,REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0,0,0}, + {"exec",execCommand,1,REDIS_CMD_DENYOOM,NULL,0,0,0,0,0}, {"discard",discardCommand,1,0,NULL,0,0,0,0,0}, {"sync",syncCommand,1,0,NULL,0,0,0,0,0}, {"flushdb",flushdbCommand,1,0,NULL,0,0,0,0,0}, @@ -186,7 +186,7 @@ struct redisCommand redisCommandTable[] = { {"psubscribe",psubscribeCommand,-2,0,NULL,0,0,0,0,0}, {"punsubscribe",punsubscribeCommand,-1,0,NULL,0,0,0,0,0}, {"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0,0,0}, - {"watch",watchCommand,-2,0,NULL,0,0,0,0,0}, + {"watch",watchCommand,-2,0,noPreloadGetKeys,1,-1,1,0,0}, {"unwatch",unwatchCommand,1,0,NULL,0,0,0,0,0} }; diff --git a/src/redis.h b/src/redis.h index cdddb601..b5188330 100644 --- a/src/redis.h +++ b/src/redis.h @@ -507,20 +507,19 @@ typedef struct pubsubPattern { } pubsubPattern; typedef void redisCommandProc(redisClient *c); -typedef void redisVmPreloadProc(redisClient *c, struct redisCommand *cmd, int argc, robj **argv); +typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags); struct redisCommand { char *name; redisCommandProc *proc; int arity; int flags; - /* Use a function to determine which keys need to be loaded - * in the background prior to executing this command. Takes precedence - * over vm_firstkey and others, ignored when NULL */ - redisVmPreloadProc *vm_preload_proc; + /* Use a function to determine keys arguments in a command line. + * Used both for diskstore preloading and Redis Cluster. */ + redisGetKeysProc *getkeys_proc; /* What keys should be loaded in background when calling this command? */ - int vm_firstkey; /* The first argument that's a key (0 = no keys) */ - int vm_lastkey; /* THe last argument that's a key */ - int vm_keystep; /* The step between first and last key */ + int firstkey; /* The first argument that's a key (0 = no keys) */ + int lastkey; /* THe last argument that's a key */ + int keystep; /* The step between first and last key */ long long microseconds, calls; }; @@ -829,8 +828,6 @@ void freeIOJob(iojob *j); void queueIOJob(iojob *j); void waitEmptyIOJobsQueue(void); void processAllPendingIOJobs(void); -void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv); -void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv); int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd); int dontWaitForSwappedKey(redisClient *c, robj *key); void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key); @@ -917,6 +914,15 @@ int selectDb(redisClient *c, int id); void signalModifiedKey(redisDb *db, robj *key); void signalFlushedDb(int dbid); +/* API to get key arguments from commands */ +#define REDIS_GETKEYS_ALL 0 +#define REDIS_GETKEYS_PRELOAD 1 +int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags); +void getKeysFreeResult(int *result); +int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); +int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); +int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); + /* Git SHA1 */ char *redisGitSHA1(void); char *redisGitDirty(void);