summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYossi Gottlieb <yossigo@gmail.com>2013-01-11 22:05:29 +0200
committerYossi Gottlieb <yossigo@gmail.com>2013-01-11 22:05:29 +0200
commit142a8099cb36d194ed92d79b7b116a8c505da8c4 (patch)
tree7718ef194a473c4937012e7b08c4c32edb2bb01c
parentb26163c2e8de2b631ef5e6e5666f8355cd7b3ae4 (diff)
downloadredis-142a8099cb36d194ed92d79b7b116a8c505da8c4.tar.gz
Add scripting persistence and better replication support.
-rw-r--r--README.Garantia15
-rwxr-xr-xsrc/rdb.c40
-rwxr-xr-xsrc/rdb.h3
-rwxr-xr-xsrc/redis.h1
-rwxr-xr-xsrc/scripting.c29
-rw-r--r--tests/unit/scripting.tcl38
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
----------------------
diff --git a/src/rdb.c b/src/rdb.c
index 52badb97d..0de78ab03 100755
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -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
diff --git a/src/rdb.h b/src/rdb.h
index e616a89f0..2fedfef94 100755
--- a/src/rdb.h
+++ b/src/rdb.h
@@ -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}
+ }
+}