diff --git a/src/redis.c b/src/redis.c index d3d426c04..5928357ae 100644 --- a/src/redis.c +++ b/src/redis.c @@ -587,6 +587,18 @@ dictType migrateCacheDictType = { NULL /* val destructor */ }; +/* Replication cached script dict (server.repl_scriptcache_dict). + * Keys are sds SHA1 strings, while values are not used at all in the current + * implementation. */ +dictType replScriptCacheDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL /* val destructor */ +}; + int htNeedsResize(dict *dict) { long long size, used; diff --git a/src/redis.h b/src/redis.h index 0c0549198..e2cfbcccd 100644 --- a/src/redis.h +++ b/src/redis.h @@ -862,6 +862,10 @@ struct redisServer { int slave_priority; /* Reported in INFO and used by Sentinel. */ char repl_master_runid[REDIS_RUN_ID_SIZE+1]; /* Master run id for PSYNC. */ long long repl_master_initial_offset; /* Master PSYNC offset. */ + /* Replication script cache. */ + dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */ + list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */ + int repl_scriptcache_size; /* Max number of elements. */ /* Limits */ unsigned int maxclients; /* Max number of simultaneous clients */ unsigned long long maxmemory; /* Max number of memory bytes to use */ @@ -1012,6 +1016,7 @@ extern dictType dbDictType; extern dictType shaScriptObjectDictType; extern double R_Zero, R_PosInf, R_NegInf, R_Nan; extern dictType hashDictType; +extern dictType replScriptCacheDictType; /*----------------------------------------------------------------------------- * Functions prototypes diff --git a/src/replication.c b/src/replication.c index fa0eb2b70..e2d5b3ec4 100644 --- a/src/replication.c +++ b/src/replication.c @@ -1432,7 +1432,85 @@ void refreshGoodSlavesCount(void) { server.repl_good_slaves_count = good; } -/* --------------------------- REPLICATION CRON ---------------------------- */ +/* ----------------------- REPLICATION SCRIPT CACHE -------------------------- + * The goal of this code is to keep track of scripts already sent to every + * connected slave, in order to be able to replicate EVALSHA as it is without + * translating it to EVAL every time it is possible. + * + * We use a capped collection implemented by an hash table for fast lookup + * of scripts we can send as EVALSHA, plus a linked list that is used for + * eviction of the oldest entry when the max number of items is reached. + * + * We don't care about taking a different cache for every different slave + * since to fill the cache again is not very costly, the goal of this code + * is to avoid that the same big script is trasmitted a big number of times + * per second wasting bandwidth and processor speed, but it is not a problem + * if we need to rebuild the cache from scratch from time to time, every used + * script will need to be transmitted a single time to reappear in the cache. + * + * This is how the system works: + * + * 1) Every time a new slave connects, we flush the whole script cache. + * 2) We only send as EVALSHA what was sent to the master as EVALSHA, without + * trying to convert EVAL into EVALSHA specifically for slaves. + * 3) Every time we trasmit a script as EVAL to the slaves, we also add the + * corresponding SHA1 of the script into the cache as we are sure every + * slave knows about the script starting from now. + * 4) On SCRIPT FLUSH command, we replicate the command to all the slaves + * and at the same time flush the script cache. + * 5) When the last slave disconnects, flush the cache. + * 6) We handle SCRIPT LOAD as well since that's how scripts are loaded + * in the master sometimes. + */ + +/* Initialize the script cache, only called at startup. */ +void replicationScriptCacheInit(void) { + server.repl_scriptcache_size = 10000; + server.repl_scriptcache_dict = dictCreate(&replScriptCacheDictType,NULL); + server.repl_scriptcache_fifo = listCreate(); +} + +/* Empty the script cache. Should be called every time we are no longer sure + * that every slave knows about all the scripts in our set, for example + * every time a new slave connects to this master and performs a full + * resynchronization. There is no need to flush the cache when a partial + * resynchronization is performed. */ +void replicationScriptCacheFlush(void) { + dictEmpty(server.repl_scriptcache_dict); + listRelease(server.repl_scriptcache_fifo); + server.repl_scriptcache_fifo = listCreate(); +} + +/* Add an entry into the script cache, if we reach max number of entries the + * oldest is removed from the list. */ +void replicationScriptCacheAdd(sds sha1) { + int retval; + sds key = sdsdup(sha1); + + /* Evict oldest. */ + if (listLength(server.repl_scriptcache_fifo) == server.repl_scriptcache_size) + { + listNode *ln = listLast(server.repl_scriptcache_fifo); + sds oldest = listNodeValue(ln); + + retval = dictDelete(server.repl_scriptcache_dict,oldest); + redisAssert(retval == DICT_OK); + listDelNode(server.repl_scriptcache_fifo,ln); + } + + /* Add current. */ + retval = dictAdd(server.repl_scriptcache_dict,key,NULL); + listAddNodeHead(server.repl_scriptcache_fifo,key); + redisAssert(retval == DICT_OK); +} + +/* Returns non-zero if the specified entry exists inside the cache, that is, + * if all the slaves are aware of this script SHA1. */ +int replicationScriptCacheExists(sds sha1) { + return dictFetchValue(server.repl_scriptcache_dict,sha1) != NULL; +} + +/* --------------------------- REPLICATION CRON ----------------------------- */ /* Replication cron funciton, called 1 time per second. */ void replicationCron(void) {