-
Notifications
You must be signed in to change notification settings - Fork 24.5k
Description
Normally setting TTL to 0 with EXPIRE or to a past timestamp with EXPIREAT on master would delete the key on both master and slave, but using lua script to do the same task will only delete the keys on master, but not slave.
Steps to reproduce,
- set some keys to master, both master and slave have some number of keys
for i in {0..100}; do redis-cli set key$i val$i > /dev/null done
MASTER
# Keyspace
db0:keys=101,expires=0,avg_ttl=0
SLAVE:
# Keyspace
db0:keys=101,expires=0,avg_ttl=0
- set TTL to 0 on all keys with lua, keys are deleted on master, but still stay on slave
for i in {0..100}; do redis-cli eval "redis.call('expire', 'key$i', 0)" 0 > /dev/null; done
MASTER:
# Keyspace
SLAVE:
# Keyspace
db0:keys=101,expires=101,avg_ttl=0
When this happens, the keys on slave will stay indefinitely and cannot be deleted since the keys are removed on master already.
I checked the source code of 4.0.10, when EXPIRE or EXPIREAT is called without using LUA, in expireGenericCommand() when the client sets the TTL to 0 or a past timestamp on the key, redis on master will delete the key immediately, then rewrites the client command to DEL with rewriteClientCommandVector(), the DEL command is then sent to slave by propagate()
if (when <= mstime() && !server.loading && !server.masterhost) {
...
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
dbSyncDelete(c->db,key);
serverAssertWithInfo(c,key,deleted);
server.dirty++;
/* Replicate/AOF this as an explicit DEL or UNLINK. */
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
rewriteClientCommandVector(c,2,aux,key);
But if EXPIRE or EXPIREAT is called with LUA, e.g. eval "redis.call('expire', 'key', 0)" 0, redis on master creates a fake client lua_client to execute the actual EXPIRE command, "expire key 0", then rewriteClientCommandVector() rewrites DEL command to the fake client, not the real client. The command in real client is still EVAL. Later when propagate() is called, it sends the command EVAL of the real client to slave, the DEL in the fake client is not replicated to the slave, causing the keys to stay indefinitely on slave. I found this behavior in both 4.0.10 and 3.2.x
Could you check whether this is expected behavior or an issue ? Thanks.