diff options
author | Yossi Gottlieb <yossigo@gmail.com> | 2013-01-11 22:05:29 +0200 |
---|---|---|
committer | Yossi Gottlieb <yossigo@gmail.com> | 2013-01-11 22:05:29 +0200 |
commit | 142a8099cb36d194ed92d79b7b116a8c505da8c4 (patch) | |
tree | 7718ef194a473c4937012e7b08c4c32edb2bb01c | |
parent | b26163c2e8de2b631ef5e6e5666f8355cd7b3ae4 (diff) | |
download | redis-142a8099cb36d194ed92d79b7b116a8c505da8c4.tar.gz |
Add scripting persistence and better replication support.
-rw-r--r-- | README.Garantia | 15 | ||||
-rwxr-xr-x | src/rdb.c | 40 | ||||
-rwxr-xr-x | src/rdb.h | 3 | ||||
-rwxr-xr-x | src/redis.h | 1 | ||||
-rwxr-xr-x | src/scripting.c | 29 | ||||
-rw-r--r-- | tests/unit/scripting.tcl | 38 |
6 files changed, 122 insertions, 4 deletions
diff --git a/README.Garantia b/README.Garantia index 0d526d298..4efc2bcce 100644 --- a/README.Garantia +++ b/README.Garantia @@ -16,6 +16,21 @@ The 'dbversion' value is stored in the RDB, which makes it incompatible with stock Redis RDB files. +Script Replication +------------------ + +This version of Redis provides a mechanism to store scripts in RDB files. As +a results, scripts are as persistent as the database itself. Also, this +allows slave servers to receive scripts when initiating a replication link. + +In addition, Redis behaves a bit differently in order to fully support +script replication: +- EVAL command with a DB-modifying script will be replicated as-is. +- EVAL command with a non-DB modifying script will be replicated as a + SCRIPT LOAD command (to save processing on the slave). +- The same applies to EVALSHA, which is replicated as an EVAL or SCRIPT LOAD. + + Different BGSAVE Types ---------------------- @@ -31,6 +31,7 @@ #include "lzf.h" /* LZF compression library */ #include "zipmap.h" #include "endianconv.h" +#include "lua.h" #include <math.h> #include <sys/types.h> @@ -700,6 +701,20 @@ int rdbSave(char *filename) { } dictReleaseIterator(di); } + + /* Store scripts */ + di = dictGetSafeIterator(server.lua_scripts); + while ((de = dictNext(di)) != NULL) { + robj key, *o = dictGetVal(de); + sds keystr = sdsnew(REDIS_RDB_SCRIPT_KEY_PREFIX); + keystr = sdscatsds(keystr, dictGetKey(de)); + keystr = sdscat(keystr, REDIS_RDB_SCRIPT_KEY_SUFFIX); + + initStaticStringObject(key, keystr); + if (rdbSaveKeyValuePair(&rdb,&key,o,-1,-1) == -1) goto werr; + } + dictReleaseIterator(di); + di = NULL; /* So that we don't release it again on error. */ /* EOF opcode */ @@ -1190,6 +1205,31 @@ int rdbLoad(char *filename) { if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; /* Read value */ if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr; + /* Handle meta-keys */ + if (val->type == REDIS_STRING && (*(char *)key->ptr & 0xff) == 0xdb) { + sds keystr = (sds) key->ptr; + + if (sdslen(keystr) == REDIS_RDB_SCRIPT_KEY_LEN && + !memcmp(keystr, REDIS_RDB_SCRIPT_KEY_PREFIX, sizeof(REDIS_RDB_SCRIPT_KEY_PREFIX)-1)) { + char funcname[43]; + robj *shakey = createStringObject(keystr + sizeof(REDIS_RDB_SCRIPT_KEY_PREFIX)-1, 40); + + funcname[0] = 'f'; + funcname[1] = '_'; + memcpy(funcname + 2, shakey->ptr, 40); + funcname[42] = 0; + + if (luaCreateFunction(NULL, server.lua, funcname, val) != REDIS_OK) { + redisLog(REDIS_WARNING, "FATAL: Data file contains an invalid LUA script.\n"); + exit(1); + } + decrRefCount(shakey); + decrRefCount(key); + + continue; + } + } + /* Check if the key already expired. This function is used when loading * an RDB file from disk, either at startup, or when an RDB was * received from the master. In the latter case, the master is @@ -42,6 +42,9 @@ /* Special key used to store dbversion in a backward-compatible manner */ #define REDIS_RDB_DBVERSION_KEY "\xDB__dbversion__\xDB" +#define REDIS_RDB_SCRIPT_KEY_PREFIX "\xDB__lua_script__" +#define REDIS_RDB_SCRIPT_KEY_SUFFIX "__\xDB" +#define REDIS_RDB_SCRIPT_KEY_LEN (sizeof(REDIS_RDB_SCRIPT_KEY_PREFIX)-1 + sizeof(REDIS_RDB_SCRIPT_KEY_SUFFIX)-1 + 40) /* Defines related to the dump file format. To store 32 bits lengths for short * keys requires a lot of space, so we check the most significant 2 bits of diff --git a/src/redis.h b/src/redis.h index 059830c1a..39066074c 100755 --- a/src/redis.h +++ b/src/redis.h @@ -1075,6 +1075,7 @@ char *sentinelHandleConfiguration(char **argv, int argc); /* Scripting */ void scriptingInit(void); +int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body); /* Git SHA1 */ char *redisGitSHA1(void); diff --git a/src/scripting.c b/src/scripting.c index d3a33388a..b8d3f519c 100755 --- a/src/scripting.c +++ b/src/scripting.c @@ -757,16 +757,20 @@ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body funcdef = sdscatlen(funcdef," end",4); if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { - addReplyErrorFormat(c,"Error compiling script (new function): %s\n", - lua_tostring(lua,-1)); + if (c != NULL) { + addReplyErrorFormat(c,"Error compiling script (new function): %s\n", + lua_tostring(lua,-1)); + } lua_pop(lua,1); sdsfree(funcdef); return REDIS_ERR; } sdsfree(funcdef); if (lua_pcall(lua,0,0,0)) { - addReplyErrorFormat(c,"Error running script (new function): %s\n", - lua_tostring(lua,-1)); + if (c != NULL) { + addReplyErrorFormat(c,"Error running script (new function): %s\n", + lua_tostring(lua,-1)); + } lua_pop(lua,1); return REDIS_ERR; } @@ -911,6 +915,22 @@ void evalGenericCommand(redisClient *c, int evalsha) { resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } + + /* When we have slaves we want to always replicate scripts, but avoid + * EVAL when a script has no impact on the slave. + */ + if (!server.lua_write_dirty) { + robj *script = createStringObject("SCRIPT", 6); + robj *load = createStringObject("LOAD", 4); + + /* Rewrite as SCRIPT LOAD. Note that SHA1 expansion has already + * been done here. + */ + rewriteClientCommandVector(c, 3, script, load, c->argv[1]); + decrRefCount(script); + decrRefCount(load); + server.dirty++; + } } void evalCommand(redisClient *c) { @@ -1004,6 +1024,7 @@ void scriptCommand(redisClient *c) { } addReplyBulkCBuffer(c,funcname+2,40); sdsfree(sha); + server.dirty++; /* Replicate this command */ } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) { if (server.lua_caller == NULL) { addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n")); diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index f1df11f3c..953807cb3 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -367,3 +367,41 @@ start_server {tags {"scripting repl"}} { } {a 1} } } + +start_server {tags {"persistent script repl"}} { + start_server {} { + test {Load read-only script 99 into the main instance using EVAL} { + r eval {return 99} 0 + } {99} + + test {Connect a slave to the main instance} { + r -1 slaveof [srv 0 host] [srv 0 port] + wait_for_condition 50 100 { + [s -1 role] eq {slave} && + [string match {*master_link_status:up*} [r -1 info replication]] + } else { + fail "Can't turn the instance into a slave" + } + } + + test {EVALSHA against slave to run script 99} { + r -1 evalsha a2eba4a6e7d6643bc4b14aa4f9e0eedd55dbae3d 0 + } {99} + + test {Load read-only script 98 into the main instance using EVAL} { + r eval {return 98} 0 + } {98} + + test {EVALSHA against slave to run script 98} { + r -1 evalsha 069e4d5d0c3afa79c9a545d8c620dbb94c2a5996 0 + } {98} + + test {Load read-only script 97 into the main instance using SCRIPT LOAD} { + r script load {return 97} + } {3f1673c4bedd6a9bc81500f26a454cd277092f80} + + test {EVALSHA against slave to run script 97} { + r -1 evalsha 3f1673c4bedd6a9bc81500f26a454cd277092f80 0 + } {97} + } +} |