summaryrefslogtreecommitdiff
path: root/src/expire.c
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2018-06-15 13:14:57 +0200
committerantirez <antirez@gmail.com>2019-06-18 12:31:10 +0200
commite9bb30fd859ed4e9e3e6434207dedbc251086858 (patch)
tree0ac8972fefe6911dee8c7376e14c611fcc105e1c /src/expire.c
parentfd0ee469ab165d0e005e9fe1fca1c4f5c604cd56 (diff)
downloadredis-new-keyspace.tar.gz
Experimental: new keyspace and expire algorithm.new-keyspace
This is an alpha quality implementation of a new keyspace representation and a new expire algorithm for Redis. This work is described here: https://gist.github.com/antirez/b2eb293819666ee104c7fcad71986eb7
Diffstat (limited to 'src/expire.c')
-rw-r--r--src/expire.c452
1 files changed, 319 insertions, 133 deletions
diff --git a/src/expire.c b/src/expire.c
index 0b92ee3fe..87c87bccd 100644
--- a/src/expire.c
+++ b/src/expire.c
@@ -38,9 +38,103 @@
* When keys are accessed they are expired on-access. However we need a
* mechanism in order to ensure keys are eventually removed when expired even
* if no access is performed on them.
+ *
+ * In order to accomplish this every key with an expire is represented in
+ * two data structures:
+ *
+ * 1. The main dictionary of keys, server.db[x]->dict, is an hash table that
+ * represents the keyspace of a given Redis database. The keys stored
+ * in the hash table are redisKey structures (typedef 'rkey'). When
+ * a key has an expire set, the key->flags have the KEY_FLAG_EXPIRE set,
+ * and the key->expire is populated with the milliseconds unix time at
+ * which the key will no longer be valid.
+ *
+ * 2. Redis also takes a radix tree that is composed only of keys that have
+ * an expire set, lexicographically sorted by the expire time. Basically
+ * each key in the radix tree is composed as follows:
+ *
+ * [8 bytes expire unix time][8 bytes key object pointer]
+ *
+ * Such tree is stored in server.db[x]->expire.
+ *
+ * The first field, the unix time, is the same stored in the key->expire of
+ * the corresponding key in the hash table, however it is stored in big endian
+ * so that sorting the time lexicographically in the tree, will also make the
+ * tree sorted by numerical expire time (from the smallest unix time to the
+ * greatest one).
+ *
+ * Then we store the key pointer, this time in native endianess, because how
+ * it is sorted does not matter, being after the unix time. If Redis is running
+ * as a 32 bit system, the last 4 bytes of the pointer are just zeroes, so
+ * we can assume a 16 bytes key in every architecture. Note that from the
+ * pointer we can retrieve the key name, lookup it in the main dictionary, and
+ * delete the key.
+ *
+ * On the other hand, when we modify the expire time of some key, we need to
+ * update the tree accordingly. At every expire cycle, what we need to do is
+ * conceptually very simple: we run the tree and expire keys as long as we
+ * find keys that are already logically expired (expire time > current time).
+ *
*----------------------------------------------------------------------------*/
-/* Helper function for the activeExpireCycle() function.
+#define EXPIRE_KEY_LEN 16 /* Key length in the radix tree of expires. */
+
+/* Populate the buffer 'buf', that should be at least EXPIRE_KEY_LEN bytes,
+ * with the key to store such key in the expires radix tree. See the comment
+ * above to see the format. */
+void encodeExpireKey(unsigned char *buf, rkey *key) {
+ uint64_t expire = htonu64(key->expire);
+ uint64_t ptr = (uint64_t) key; /* The pointer may be 32 bit, cast to 64. */
+ memcpy(buf,&expire,sizeof(expire));
+ memcpy(buf+8,&ptr,sizeof(ptr));
+}
+
+/* This is the reverse of encodeExpireKey(): given the key will return a
+ * pointer to an rkey and the expire value. */
+void decodeExpireKey(unsigned char *buf, uint64_t *expireptr, rkey **keyptrptr) {
+ uint64_t expire;
+ uint64_t keyptr;
+ memcpy(&expire,buf,sizeof(expire));
+ expire = ntohu64(expire);
+ memcpy(&keyptr,buf+8,sizeof(keyptr));
+ *expireptr = expire;
+ *keyptrptr = (rkey*)(unsigned long)keyptr;
+}
+
+/* Populate the expires radix tree with the specified key. */
+void addExpireToTree(redisDb *db, rkey *key) {
+ unsigned char expirekey[EXPIRE_KEY_LEN];
+ encodeExpireKey(expirekey,key);
+ int retval = raxTryInsert(db->expires,expirekey,EXPIRE_KEY_LEN,NULL,NULL);
+ serverAssert(retval != 0);
+}
+
+/* Remove the specified key from the expires radix tree. */
+void removeExpireFromTree(redisDb *db, rkey *key) {
+ unsigned char expirekey[EXPIRE_KEY_LEN];
+ encodeExpireKey(expirekey,key);
+ int retval = raxRemove(db->expires,expirekey,EXPIRE_KEY_LEN,NULL);
+ serverAssert(retval != 0);
+}
+
+/* Delete a key that is found expired by the expiration cycle. We need to
+ * propagate the key too, send the notification event, and take a few
+ * stats. */
+void deleteExpiredKey(redisDb *db, rkey *key) {
+ robj *keyname = createStringObject(key->name,key->len);
+
+ propagateExpire(db,keyname,server.lazyfree_lazy_expire);
+ if (server.lazyfree_lazy_expire)
+ dbAsyncDelete(db,keyname);
+ else
+ dbSyncDelete(db,keyname);
+ notifyKeyspaceEvent(NOTIFY_EXPIRED,
+ "expired",keyname,db->id);
+ decrRefCount(keyname);
+ server.stat_expiredkeys++;
+}
+
+/* Helper function for the expireSlaveKeys() function.
* This function will try to expire the key that is stored in the hash table
* entry 'de' of the 'expires' hash table of a Redis database.
*
@@ -51,21 +145,10 @@
*
* The parameter 'now' is the current time in milliseconds as is passed
* to the function to avoid too many gettimeofday() syscalls. */
-int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
- long long t = dictGetSignedIntegerVal(de);
+int activeExpireCycleTryExpire(redisDb *db, rkey *key, long long now) {
+ long long t = key->expire;
if (now > t) {
- sds key = dictGetKey(de);
- robj *keyobj = createStringObject(key,sdslen(key));
-
- propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
- if (server.lazyfree_lazy_expire)
- dbAsyncDelete(db,keyobj);
- else
- dbSyncDelete(db,keyobj);
- notifyKeyspaceEvent(NOTIFY_EXPIRED,
- "expired",keyobj,db->id);
- decrRefCount(keyobj);
- server.stat_expiredkeys++;
+ deleteExpiredKey(db,key);
return 1;
} else {
return 0;
@@ -101,7 +184,8 @@ void activeExpireCycle(int type) {
static int timelimit_exit = 0; /* Time limit hit in previous call? */
static long long last_fast_cycle = 0; /* When last fast cycle ran. */
- int j, iteration = 0;
+ int j;
+ unsigned long iteration = 0;
int dbs_per_call = CRON_DBS_PER_CALL;
long long start = ustime(), timelimit, elapsed;
@@ -129,25 +213,20 @@ void activeExpireCycle(int type) {
if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum;
- /* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
- * per iteration. Since this function gets called with a frequency of
- * server.hz times per second, the following is the max amount of
- * microseconds we can spend in this function. */
+ /* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of
+ * CPU time per iteration. Since this function gets called with a
+ * frequency of server.hz times per second, the following is the max
+ * amount of microseconds we can spend in this function. */
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1;
+ /* If it's a fast cycle, override the time limit with our fixed
+ * time limit (defaults to 1 millisecond). */
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
- /* Accumulate some global stats as we expire keys, to have some idea
- * about the number of keys that are already logically expired, but still
- * existing inside the database. */
- long total_sampled = 0;
- long total_expired = 0;
-
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
- int expired;
redisDb *db = server.db+(current_db % server.dbnum);
/* Increment the DB now so we are sure if we run out of time
@@ -155,92 +234,59 @@ void activeExpireCycle(int type) {
* distribute the time evenly across DBs. */
current_db++;
- /* Continue to expire if at the end of the cycle more than 25%
- * of the keys were expired. */
- do {
- unsigned long num, slots;
- long long now, ttl_sum;
- int ttl_samples;
+ /* If there is nothing to expire try next DB ASAP, avoiding the
+ * cost of seeking the radix tree iterator. */
+ if (raxSize(db->expires) == 0) continue;
+
+ /* The main collection cycle. Run the tree and expire keys that
+ * are found to be already logically expired. */
+ long long now = mstime();
+ raxIterator ri;
+ raxStart(&ri,db->expires);
+ raxSeek(&ri,"^",NULL,0);
+
+ /* Enter the loop expiring keys for this database. Inside this
+ * loop there are two stop conditions:
+ *
+ * 1. The time limit.
+ * 2. The loop will exit if in this DB there are no more keys
+ * that are logically expired.
+ *
+ * Moreover the loop naturally terminates when there are no longer
+ * elements in the radix tree. */
+ while(raxNext(&ri)) {
+ rkey *key;
+ uint64_t expire;
+ decodeExpireKey(ri.key,&expire,&key);
+
+ /* First stop condition: no keys to expire here. */
+ if (expire >= (uint64_t)now) break;
+
+ printf("DEL %.*s -> %llu\n", (int)key->len, key->name, expire);
+ deleteExpiredKey(db,key);
+
+ /* Second stop condition: the time limit. */
iteration++;
-
- /* If there is nothing to expire try next DB ASAP. */
- if ((num = dictSize(db->expires)) == 0) {
- db->avg_ttl = 0;
- break;
- }
- slots = dictSlots(db->expires);
- now = mstime();
-
- /* When there are less than 1% filled slots getting random
- * keys is expensive, so stop here waiting for better times...
- * The dictionary will be resized asap. */
- if (num && slots > DICT_HT_INITIAL_SIZE &&
- (num*100/slots < 1)) break;
-
- /* The main collection cycle. Sample random keys among keys
- * with an expire set, checking for expired ones. */
- expired = 0;
- ttl_sum = 0;
- ttl_samples = 0;
-
- if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
- num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
-
- while (num--) {
- dictEntry *de;
- long long ttl;
-
- if ((de = dictGetRandomKey(db->expires)) == NULL) break;
- ttl = dictGetSignedIntegerVal(de)-now;
- if (activeExpireCycleTryExpire(db,de,now)) expired++;
- if (ttl > 0) {
- /* We want the average TTL of keys yet not expired. */
- ttl_sum += ttl;
- ttl_samples++;
- }
- total_sampled++;
- }
- total_expired += expired;
-
- /* Update the average TTL stats for this database. */
- if (ttl_samples) {
- long long avg_ttl = ttl_sum/ttl_samples;
-
- /* Do a simple running average with a few samples.
- * We just use the current estimate with a weight of 2%
- * and the previous estimate with a weight of 98%. */
- if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
- db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
- }
-
- /* We can't block forever here even if there are many keys to
- * expire. So after a given amount of milliseconds return to the
- * caller waiting for the other active expire cycle. */
- if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
- elapsed = ustime()-start;
+ if ((iteration & 0xff) == 0) {
+ now = ustime();
+ elapsed = now-start;
+ now /= 1000; /* Convert back now to milliseconds. */
if (elapsed > timelimit) {
timelimit_exit = 1;
+ printf("LIMIT (%llu) type:%d [elapsed=%llu]\n", timelimit, type, elapsed);
server.stat_expired_time_cap_reached_count++;
break;
}
}
- /* We don't repeat the cycle if there are less than 25% of keys
- * found expired in the current DB. */
- } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
+ /* Reseek the iterator: the node we were on is now
+ * deleted. */
+ raxSeek(&ri,"^",NULL,0);
+ }
+ raxStop(&ri);
}
elapsed = ustime()-start;
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
-
- /* Update our estimate of keys existing but yet to be expired.
- * Running average with this sample accounting for 5%. */
- double current_perc;
- if (total_sampled) {
- current_perc = (double)total_expired/total_sampled;
- } else
- current_perc = 0;
- server.stat_expired_stale_perc = (current_perc*0.05)+
- (server.stat_expired_stale_perc*0.95);
}
/*-----------------------------------------------------------------------------
@@ -269,8 +315,8 @@ void activeExpireCycle(int type) {
/* The dictionary where we remember key names and database ID of keys we may
* want to expire from the slave. Since this function is not often used we
* don't even care to initialize the database at startup. We'll do it once
- * the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
- * is called.
+ * the feature is used the first time, that is, when the function
+ * rememberSlaveKeyWithExpire() is called.
*
* The dictionary has an SDS string representing the key as the hash table
* key, while the value is a 64 bit unsigned integer with the bits corresponding
@@ -300,11 +346,12 @@ void expireSlaveKeys(void) {
while(dbids && dbid < server.dbnum) {
if ((dbids & 1) != 0) {
redisDb *db = server.db+dbid;
- dictEntry *expire = dictFind(db->expires,keyname);
+ rkey *key = dictFetchValue(db->dict,keyname);
+ if (!(key->flags & KEY_FLAG_EXPIRE)) key = NULL;
int expired = 0;
- if (expire &&
- activeExpireCycleTryExpire(server.db+dbid,expire,start))
+ if (key &&
+ activeExpireCycleTryExpire(server.db+dbid,key,start))
{
expired = 1;
}
@@ -313,7 +360,7 @@ void expireSlaveKeys(void) {
* corresponding bit in the new bitmap we set as value.
* At the end of the loop if the bitmap is zero, it means we
* no longer need to keep track of this key. */
- if (expire && !expired) {
+ if (key && !expired) {
noexpire++;
new_dbids |= (uint64_t)1 << dbid;
}
@@ -341,13 +388,15 @@ void expireSlaveKeys(void) {
/* Track keys that received an EXPIRE or similar command in the context
* of a writable slave. */
-void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
+void rememberSlaveKeyWithExpire(redisDb *db, rkey *key) {
if (slaveKeysWithExpire == NULL) {
static dictType dt = {
- dictSdsHash, /* hash function */
+ dictSdsHash, /* lookup hash function */
+ dictSdsHash, /* stored hash function */
NULL, /* key dup */
NULL, /* val dup */
- dictSdsKeyCompare, /* key compare */
+ dictSdsKeyCompare, /* loopkup key compare */
+ dictSdsKeyCompare, /* stored key compare */
dictSdsDestructor, /* key destructor */
NULL /* val destructor */
};
@@ -355,13 +404,15 @@ void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
}
if (db->id > 63) return;
- dictEntry *de = dictAddOrFind(slaveKeysWithExpire,key->ptr);
- /* If the entry was just created, set it to a copy of the SDS string
- * representing the key: we don't want to need to take those keys
- * in sync with the main DB. The keys will be removed by expireSlaveKeys()
- * as it scans to find keys to remove. */
- if (de->key == key->ptr) {
- de->key = sdsdup(key->ptr);
+ sds skey = sdsnewlen(key->name,key->len);
+ dictEntry *de = dictAddOrFind(slaveKeysWithExpire,skey);
+ /* If the entry was already there, free the SDS string we used to lookup.
+ * Note that we don't care to take those keys in sync with the
+ * main DB. The keys will be removed by expireSlaveKeys() as it scans to
+ * find keys to remove. */
+ if (de->key != skey) {
+ sdsfree(skey);
+ } else {
dictSetUnsignedIntegerVal(de,0);
}
@@ -392,6 +443,137 @@ void flushSlaveKeysWithExpireList(void) {
}
/*-----------------------------------------------------------------------------
+ * Expires API
+ *----------------------------------------------------------------------------*/
+
+/* Remove the expire from the key making it persistent. */
+int removeExpire(redisDb *db, rkey *key) {
+ if (!(key->flags & KEY_FLAG_EXPIRE)) return 0;
+ removeExpireFromTree(db,key);
+ key->flags &= ~KEY_FLAG_EXPIRE;
+ key->expire = 0; /* Not needed but better to leave the object clean. */
+ return 1;
+}
+
+/* Set an expire to the specified key. If the expire is set in the context
+ * of an user calling a command 'c' is the client, otherwise 'c' is set
+ * to NULL. The 'when' parameter is the absolute unix time in milliseconds
+ * after which the key will no longer be considered valid. */
+void setExpire(client *c, redisDb *db, rkey *key, long long when) {
+ /* Reuse the sds from the main dict in the expire dict */
+ if (key->flags & KEY_FLAG_EXPIRE) removeExpireFromTree(db,key);
+ key->flags |= KEY_FLAG_EXPIRE;
+ key->expire = when;
+ addExpireToTree(db,key);
+
+ int writable_slave = server.masterhost && server.repl_slave_ro == 0;
+ if (c && writable_slave && !(c->flags & CLIENT_MASTER))
+ rememberSlaveKeyWithExpire(db,key);
+}
+
+/* Return the expire time of the specified key, or -1 if no expire
+ * is associated with this key (i.e. the key is non volatile) */
+long long getExpire(rkey *key) {
+ return (key->flags & KEY_FLAG_EXPIRE) ? key->expire : -1;
+}
+
+/* Propagate expires into slaves and the AOF file.
+ * When a key expires in the master, a DEL operation for this key is sent
+ * to all the slaves and the AOF file if enabled.
+ *
+ * This way the key expiry is centralized in one place, and since both
+ * AOF and the master->slave link guarantee operation ordering, everything
+ * will be consistent even if we allow write operations against expiring
+ * keys. */
+void propagateExpire(redisDb *db, robj *key, int lazy) {
+ robj *argv[2];
+
+ argv[0] = lazy ? shared.unlink : shared.del;
+ argv[1] = key;
+ incrRefCount(argv[0]);
+ incrRefCount(argv[1]);
+
+ if (server.aof_state != AOF_OFF)
+ feedAppendOnlyFile(server.delCommand,db->id,argv,2);
+ replicationFeedSlaves(server.slaves,db->id,argv,2);
+
+ decrRefCount(argv[0]);
+ decrRefCount(argv[1]);
+}
+
+/* Check if the key is expired. */
+int keyIsExpired(rkey *key) {
+ mstime_t when = getExpire(key);
+
+ 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 pretend 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. */
+ mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
+
+ return now > when;
+}
+
+/* This function is called when we are going to perform some operation
+ * in a given key, but such key may be already logically expired even if
+ * it still exists in the database. The main way this function is called
+ * is via lookupKey*() family of functions.
+ *
+ * The behavior of the function depends on the replication role of the
+ * instance, because slave instances do not expire keys, they wait
+ * for DELs from the master for consistency matters. However even
+ * slaves will try to have a coherent return value for the function,
+ * so that read commands executed in the slave side will be able to
+ * behave like if the key is expired even if still present (because the
+ * master has yet to propagate the DEL).
+ *
+ * In masters as a side effect of finding a key which is expired, such
+ * key will be evicted from the database. Also this may trigger the
+ * propagation of a DEL/UNLINK command in AOF / replication stream.
+ *
+ * The return value of the function is 0 if the key is still valid,
+ * otherwise the function returns 1 if the key is expired. */
+int expireIfNeeded(redisDb *db, robj *keyname, rkey *key) {
+ if (!keyIsExpired(key)) return 0;
+
+ /* If we are running in the context of a slave, instead of
+ * evicting the expired key from the database, we return ASAP:
+ * the slave key expiration is controlled by the master that will
+ * send us synthesized DEL operations for expired keys.
+ *
+ * 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 1;
+
+ /* Delete the key */
+ server.stat_expiredkeys++;
+ propagateExpire(db,keyname,server.lazyfree_lazy_expire);
+ notifyKeyspaceEvent(NOTIFY_EXPIRED,
+ "expired",keyname,db->id);
+ return server.lazyfree_lazy_expire ? dbAsyncDelete(db,keyname) :
+ dbSyncDelete(db,keyname);
+}
+
+/* Sometimes we have just the name of the key, because we have still to
+ * lookup it. In such cases this function is more handy compared to
+ * expireIfNeeded(): just a wrapper performing the lookup first. */
+int expireIfNeededByName(redisDb *db, robj *keyname) {
+ rkey *key;
+ robj *val = lookupKey(db,keyname,&key,LOOKUP_NOTOUCH);
+ if (!val) return 0;
+ return expireIfNeeded(db,keyname,key);
+}
+
+
+
+/*-----------------------------------------------------------------------------
* Expires Commands
*----------------------------------------------------------------------------*/
@@ -403,7 +585,8 @@ void flushSlaveKeysWithExpireList(void) {
* unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
* the argv[2] parameter. The basetime is always specified in milliseconds. */
void expireGenericCommand(client *c, long long basetime, int unit) {
- robj *key = c->argv[1], *param = c->argv[2];
+ robj *keyname = c->argv[1], *param = c->argv[2];
+ rkey *key;
long long when; /* unix time in milliseconds when the key will expire. */
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
@@ -413,7 +596,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
when += basetime;
/* No key, return zero. */
- if (lookupKeyWrite(c->db,key) == NULL) {
+ if (lookupKeyWrite(c->db,keyname,&key) == NULL) {
addReply(c,shared.czero);
return;
}
@@ -427,23 +610,24 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
if (when <= mstime() && !server.loading && !server.masterhost) {
robj *aux;
- int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
- dbSyncDelete(c->db,key);
- serverAssertWithInfo(c,key,deleted);
+ int deleted = server.lazyfree_lazy_expire ?
+ dbAsyncDelete(c->db,keyname) :
+ dbSyncDelete(c->db,keyname);
+ serverAssertWithInfo(c,keyname,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);
- signalModifiedKey(c->db,key);
- notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
+ rewriteClientCommandVector(c,2,aux,keyname);
+ signalModifiedKey(c->db,keyname);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"del",keyname,c->db->id);
addReply(c, shared.cone);
return;
} else {
setExpire(c,c->db,key,when);
addReply(c,shared.cone);
- signalModifiedKey(c->db,key);
- notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
+ signalModifiedKey(c->db,keyname);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",keyname,c->db->id);
server.dirty++;
return;
}
@@ -472,15 +656,16 @@ void pexpireatCommand(client *c) {
/* Implements TTL and PTTL */
void ttlGenericCommand(client *c, int output_ms) {
long long expire, ttl = -1;
+ rkey *key;
/* If the key does not exist at all, return -2 */
- if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) {
+ if (lookupKeyReadWithFlags(c->db,c->argv[1],&key,LOOKUP_NOTOUCH) == NULL) {
addReplyLongLong(c,-2);
return;
}
/* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */
- expire = getExpire(c->db,c->argv[1]);
+ expire = getExpire(key);
if (expire != -1) {
ttl = expire-mstime();
if (ttl < 0) ttl = 0;
@@ -504,8 +689,9 @@ void pttlCommand(client *c) {
/* PERSIST key */
void persistCommand(client *c) {
- if (lookupKeyWrite(c->db,c->argv[1])) {
- if (removeExpire(c->db,c->argv[1])) {
+ rkey *key;
+ if (lookupKeyWrite(c->db,c->argv[1],&key)) {
+ if (removeExpire(c->db,key)) {
addReply(c,shared.cone);
server.dirty++;
} else {
@@ -520,7 +706,7 @@ void persistCommand(client *c) {
void touchCommand(client *c) {
int touched = 0;
for (int j = 1; j < c->argc; j++)
- if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++;
+ if (lookupKeyRead(c->db,c->argv[j],NULL) != NULL) touched++;
addReplyLongLong(c,touched);
}