summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2014-02-03 16:15:53 +0100
committerantirez <antirez@gmail.com>2014-02-03 16:29:25 +0100
commitc5bc59265043332b925ec1ffaa9498a31e5df7bb (patch)
tree6657497df187339755d59ee33fc6740a3b613636
parent5201ca0ca1d88f6f9680b0380cd8d8c46a834ea5 (diff)
downloadredis-c5bc59265043332b925ec1ffaa9498a31e5df7bb.tar.gz
Scripting: expire keys in scripts only at first access.
Keys expiring in the middle of the execution of Lua scripts are to create inconsistencies in masters and / or AOF files. See the following example: if redis.call("exists",KEYS[1]) == 1 then redis.call("incr","mycounter") end if redis.call("exists",KEYS[1]) == 1 then return redis.call("incr","mycounter") end The script executes two times the same *if key exists then incrementcounter* logic. However the two executions will work differently in the master and the slaves, provided some unlucky timing happens. In the master the first time the key may still exist, while the second time the key may no longer exist. This will result in the key incremented just one time. However as a side effect the master will generate a synthetic `DEL` command in the replication channel in order to force the slaves to expire the key (given that key expiration is master-driven). When the same script will run in the slave, the key will no longer be there, so the script will not increment the key. The key idea used to implement the expire-at-first-lookup semantics was provided by Marc Gravell.
-rw-r--r--src/db.c16
1 files changed, 11 insertions, 5 deletions
diff --git a/src/db.c b/src/db.c
index b625fea53..0309363f2 100644
--- a/src/db.c
+++ b/src/db.c
@@ -755,13 +755,21 @@ void propagateExpire(redisDb *db, robj *key) {
}
int expireIfNeeded(redisDb *db, robj *key) {
- long long when = getExpire(db,key);
+ mstime_t when = getExpire(db,key);
+ mstime_t now;
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
+ /* If we are in the context of a Lua script, we claim that time is
+ * blocked to when the Lua script started. This way a key can expire
+ * only the first time it is accessed and not in the middle of the
+ * script execution, making propagation to slaves / AOF consistent.
+ * See issue #1525 on Github for more information. */
+ now = server.lua_caller ? server.lua_time_start : mstime();
+
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
@@ -769,12 +777,10 @@ int expireIfNeeded(redisDb *db, robj *key) {
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
- if (server.masterhost != NULL) {
- return mstime() > when;
- }
+ if (server.masterhost != NULL) return now > when;
/* Return when this key has not expired */
- if (mstime() <= when) return 0;
+ if (now <= when) return 0;
/* Delete the key */
server.stat_expiredkeys++;